<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<atom:link href="https://www.yono233.cn/feed" rel="self" type="application/rss+xml"/>
<title>土星环的基地</title>
<link>https://www.yono233.cn</link>
<description>朋友没来齐，派对不结束</description>
<language>zh-CN</language>
<copyright>© yono </copyright>
<pubDate>Tue, 15 Sep 2026 08:30:58 GMT</pubDate>
<generator>Mix Space CMS (https://github.com/mx-space)</generator>
<docs>https://mx-space.js.org</docs>
<image>
    <url>https://cloudflare-imgbed-6qt.pages.dev/file/1735626843710_ic.jpg</url>
    <title>土星环的基地</title>
    <link>https://www.yono233.cn</link>
</image>
<item>
    <title>git 提交回撤备查</title>
    <link>https://www.yono233.cn/notes/5</link>
    <pubDate>Tue, 18 Aug 2026 10:39:46 GMT</pubDate>
    <description>遇到 git 误提交的情况，但是还没有上传，该如何回退到上次提交并进行额外的修改。因为本质笨逼每次都</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/notes/5'>https://www.yono233.cn/notes/5</a></blockquote>
          <p>遇到 git 误提交的情况，但是还没有上传，该如何回退到上次提交并进行额外的修改。因为本质笨逼每次都要查，让 AI 梳理了最简指导手册。</p>
<h2>一、撤销「上一次提交」但保留改动（你的场景）</h2>
<p>  最常见的需求：误提交了，想退回上次提交、接着改，改完再重新提交。</p>
<blockquote>
<p>  git reset --soft HEAD<del>1      # 撤销提交，改动回到「暂存区」
  或
  git reset HEAD</del>1             # 撤销提交，改动回到「工作区」（未暂存）</p>
</blockquote>
<ul>
<li>HEAD~1 = 上一次提交（父提交）</li>
<li>然后你正常改文件 → git add → git commit 重新提交即可</li>
</ul>
<p>  reset 三种模式，记这一个表就够了：</p>
<table>
<thead>
<tr>
<th>命令</th>
<th>HEAD</th>
<th>暂存区</th>
<th>工作区文件</th>
<th>效果</th>
</tr>
</thead>
<tbody><tr>
<td>git reset --soft HEAD~1</td>
<td>回退</td>
<td>保留</td>
<td>保留</td>
<td>撤销提交，改动还在暂存区</td>
</tr>
<tr>
<td>git reset HEAD~1（默认 mixed）</td>
<td>回退</td>
<td>重置</td>
<td>保留</td>
<td>撤销提交，改动回到未暂存</td>
</tr>
<tr>
<td>git reset --hard HEAD~1</td>
<td>回退</td>
<td>重置</td>
<td>清空</td>
<td>撤销提交并丢弃改动</td>
</tr>
</tbody></table>
<p>  ▎ --hard 会删掉改动，慎用。想「退回上次提交、又要接着改」永远用 --soft 或默认的 mixed。</p>
<h2>二、只是想给上次提交「补点东西」</h2>
<p>  不需要撤销重来，直接把新改动并进去：</p>
<pre><code class="language-">  git add 改动的文件
  git commit --amend --no-edit   # 并入上次提交，消息不变
  git commit --amend             # 并入 + 顺便改提交消息</code></pre><h2>三、特殊情况：只有一次提交</h2>
<p>  HEAD<del>1 要求至少有 2 次提交。像你这个仓库之前只有 ee01887 一条，HEAD</del>1 会报错——这时直接 git commit --amend
  就行，它天然就是「在这一次提交上改」。</p>
<h2>四、救命稻草：reflog</h2>
<p>  不管用 reset 还是 amend 改错了，历史都没真丢：</p>
<blockquote>
<p>  git reflog                # 列出你所有的 HEAD 移动记录
  git reset --hard &lt;某条id&gt;  # 回到任意一个时间点</p>
</blockquote>
<p>  只要没 git gc --prune=now 清掉，reflog 里的提交都能找回。</p>
<hr>
<p>  一句话总结：没 push → 撤销用 git reset --soft HEAD~1，补改用 git commit --amend，改崩了看 git reflog。</p>

          <p style='text-align: right'>
          <a href='https://www.yono233.cn/notes/5#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">171570463489658880</guid>
  <category>note</category>
false
 </item>
  <item>
    <title>NI 驱动导致 win 蓝屏</title>
    <link>https://www.yono233.cn/posts/novel/26_7_31_ni-win</link>
    <pubDate>Fri, 31 Jul 2026 07:30:11 GMT</pubDate>
    <description>现象

windows 笔记本合盖睡眠，且不接电源的情况下。经过随机 1~3 小时。

再开盖时，按</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/posts/novel/26_7_31_ni-win'>https://www.yono233.cn/posts/novel/26_7_31_ni-win</a></blockquote>
          <h1>现象</h1>
<p>windows 笔记本合盖睡眠，且不接电源的情况下。经过随机 1~3 小时。</p>
<p>再开盖时，按下电源笔记本无法正常开启，经过 1<del>3 分钟，会蓝屏并报出 <code>DRIVER_POWER_STATE_FAILURE (0x9F)</code> 错误码，随后经过 2</del>5 分钟自检并彻底重启。</p>
<p>这非常打断工作流，重启后所有的工作区和软件都要重新打开，特别是在国内的傻逼软件<strong>微信系</strong>的玩意，都必须再手动掏出手机，然后扫码，然后他又得过一段时间同步信息，有时还会丢失信息。</p>
<p>经过这样一次事件，需要几乎半小时，才能准备好一切工作条件再重新开始。</p>
<h1>排查</h1>
<p>我一直以为是 windows 某次更新后引入的奇怪 bug，或者硬件上的某种不适配。今天受不了决定了解下具体是什么问题。</p>
<p>好在现在有 AI 帮助，只要自己检索规划出合理的排查路径，大多数信息都可以询问 AI 而不用自己从零学习。</p>
<p>主要的参考文章是</p>
<p><a href="https://www.cnblogs.com/xiykj/p/13304725.html">Windows蓝屏Dump文件分析 - 凡是過往；皆為序章 - 博客园</a></p>
<p>介绍了如何分析蓝屏现场保存的 windows 崩溃记录文件 Dump，该系列文件在 <strong>C:\Windows\Minidump</strong>，为了避免权限问题，复制一份放在外面进行分析。</p>
<h2>WinDbg</h2>
<p>windows 有 <strong>WinDbg</strong> 工具，参考文章中的版本相对老旧，所以需要基本探索一下 <strong>WinDbg</strong> 工具的使用。</p>
<p>微软官方有安装指南 <a href="https://learn.microsoft.com/zh-cn/windows-hardware/drivers/debugger/">安装 WinDbg - Windows drivers | Microsoft Learn</a>，最方便快捷的就是使用终端指令安装，在终端运行</p>
<pre><code class="language-sh">winget install Microsoft.WinDbg</code></pre><p>或者你的终端环境不可用，也可以选用指南中的其他方法。安装完成后在开始菜单的应用列表中搜索 <strong>WinDbg</strong>，即可打开。</p>
<p>与参考文章不同，新版本的 <strong>WinDbg</strong> 工具操作方法和设置项名有一些区别，下面会逐步演示。</p>
<p>在正式导入 Dump 文件分析前需要做预配置。</p>
<p>首先点击左上角的 <strong>File</strong>。</p>
<p></p>
<p>然后点击 <strong>Settings</strong>，打开弹出页的 <strong>Debugging settings</strong>，设置其中的 <strong>Symbol path</strong>，为</p>
<pre><code class="language-">srv*C:\Symbols*http://msdl.microsoft.com/download/symbols</code></pre><p>这里我的操作方法和参考文章就已经不同，微软系的这些小软件，未来更新可能操作逻辑又有变化。</p>
<p>宗旨是预先配置 <strong>Symbol</strong> 这个关键词相关的设置项就行。</p>
<p></p>
<p>然后来到 <strong>Start debugging</strong>，点击其中的 <strong>Open dump file</strong>。这里我是灰色因为我在进行一个 dump 分析且暂时没有保存或关闭文件。</p>
<p>这一步的操作逻辑也未必一致，宗旨是找到 <strong>Open dump</strong> 这个关键词。</p>
<p></p>
<p>如果成功打开，那么将会有一些头信息的输出，并提示你运行相应指令进行正式分析。</p>
<p>你只要打命令开始分析就好</p>
<pre><code class="language-">!analyze -v</code></pre><p>注意，这个分析有一定耗时且不会给你提示，耐心等待 Debugging Details: 后的信息完全输出。当他分析完毕后，Debugging Details: 信息会不间断一次性输出。</p>
<p></p>
<p>然后你就可以成批量将输出的分析信息一次性包给 AI 分析了，实际上我也完全看不懂。</p>
<h2>我的问题分析结论</h2>
<p>AI 给出的分析结论如下。</p>
<pre><code class="language-sh">∴ The user has provided the complete !analyze -v output along with the !devobj and !irp commands I requested. Now I can
  give a comprehensive analysis.

  Let me parse the key findings:

  1. Bug Check: 0x9F (DRIVER_POWER_STATE_FAILURE) with Arg1=3 (device object blocking IRP too long)
  2. IMAGE_NAME: pci.sys
  3. FAILURE_BUCKET: 0x9F_3_POWER_DOWN_nipcibrd_IMAGE_pci.sys
  4. DRVPOWERSTATE_SUBCODE: 3 — power down transition
  5. STACK_TEXT: Shows nt!PopIrpWatchdogBugcheck → nt!PopIrpWatchdog — the power watchdog timer fired, meaning an IRP
  was stuck too long

  From !irp output:
  - Current stack (stack 7): IRP_MJ_POWER(16), IRP_MN_SET_POWER(2) — this is a SET_POWER IRP
  - The driver handling it is nipcibrd.sys — this is the National Instruments PCI/PXI bridge driver
  - The status shows "Success Error Cancel pending" — the IRP is still pending
  - The completion routine is nt!PopRequestCompletion
  - The device stack shows: \Driver\nipcibrd → \Driver\pci → \Driver\ACPI

  From !devobj:
  - The PDO belongs to \Driver\pci
  - Upper attached device is \Driver\ACPI

  So the power IRP is going through: pci → ACPI → nipcibrd

  The nipcibrd.sys driver has the pending IRP — it's not completing the power down request.

  Let me give a thorough analysis.

● 完整分析结论

  崩溃根因

  肇事驱动：nipcibrd.sys — National Instruments PCI/PXI Bridge Driver

  这是一个 National Instruments (NI) 的驱动，用于 NI 的 PCI/PXI 数据采集或仪器控制硬件。

  ---
  证据链

  ┌──────────────────────┬──────────────────────────────────────────────────────────────────┐
  │         证据         │                               指向                               │
  ├──────────────────────┼──────────────────────────────────────────────────────────────────┤
  │ FAILURE_BUCKET       │ 0x9F_3_POWER_DOWN_nipcibrd_IMAGE_pci.sys                         │
  ├──────────────────────┼──────────────────────────────────────────────────────────────────┤
  │ IRP 当前栈 (stack 7) │ IRP_MJ_POWER(16), IRP_MN_SET_POWER(2) — 由 \Driver\nipcibrd 持有 │
  ├──────────────────────┼──────────────────────────────────────────────────────────────────┤
  │ IRP 状态             │ Success Error Cancel pending — 仍在 pending，未完成              │
  ├──────────────────────┼──────────────────────────────────────────────────────────────────┤
  │ 子代码               │ DRVPOWERSTATE_SUBCODE: 3 — 电源下降转换 (S0→S3/S4/关机)          │
  ├──────────────────────┼──────────────────────────────────────────────────────────────────┤
  │ 设备栈               │ nipcibrd → pci → ACPI                                            │
  └──────────────────────┴──────────────────────────────────────────────────────────────────┘

  ---
  故障机制

  1. 系统发起电源状态转换（睡眠/休眠/关机），向设备栈发送 IRP_MN_SET_POWER IRP
  2. IRP 沿设备栈传递：ACPI → pci → nipcibrd
  3. nipcibrd.sys 收到了这个电源 IRP，但没有在时限内完成它（一直保持 pending 状态）
  4. 电源管理看门狗 (PopIrpWatchdog) 超时（默认约 10 分钟），触发 0x9F 蓝屏

  调用栈也完全印证：
  nt!PopIrpWatchdogBugcheck    ← 电源看门狗触发蓝屏
  nt!PopIrpWatchdog            ← 检测到 IRP 超时
  nt!KiProcessExpiredTimerList ← 定时器到期
  nt!KiTimerExpiration
  nt!KiRetireDpcList
  nt!KiIdleLoop

  ---
  解决建议

  ┌────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ 优先级 │                                                  操作                                                   │
  ├────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 1 立即 │ 更新 NI 驱动 — 去 ni.com/drivers 下载最新版 NI 设备驱动（NI-DAQmx、NI-VISA、或 NI-488.2，视你的 NI      │
  │        │ 硬件而定）                                                                                              │
  ├────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  2. IRP 沿设备栈传递：ACPI → pci → nipcibrd
  3. nipcibrd.sys 收到了这个电源 IRP，但没有在时限内完成它（一直保持 pending 状态）
  4. 电源管理看门狗 (PopIrpWatchdog) 超时（默认约 10 分钟），触发 0x9F 蓝屏

  调用栈也完全印证：
  nt!PopIrpWatchdogBugcheck    ← 电源看门狗触发蓝屏
  nt!PopIrpWatchdog            ← 检测到 IRP 超时
  nt!KiProcessExpiredTimerList ← 定时器到期
  nt!KiTimerExpiration
  nt!KiRetireDpcList
  nt!KiIdleLoop

  ---
  解决建议

  ┌────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ 优先级 │                                                     操作                                                      │
  ├────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 1 立即 │ 更新 NI 驱动 — 去 ni.com/drivers 下载最新版 NI 设备驱动（NI-DAQmx、NI-VISA、或 NI-488.2，视你的 NI 硬件而定） │
  ├────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 2 立即 │ 检查是否有 NI 硬件连接到这台电脑（PCI 卡、PXI 机箱、USB DAQ 等），确认固件也是最新的                          │
  ├────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 3 检查 │ 运行 driverquery /v 查看 nipcibrd.sys 的版本号，当前崩溃版本 10.0.26100.8737 可能不兼容 Windows 11 24H2       │
  ├────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 4 临时 │ 如果暂时不需要 NI 硬件，可以在设备管理器中禁用 NI 相关设备，或卸载 NI 软件                                    │
  ├────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ 5 长期 │ 在 NI 更新驱动之前，避免让系统进入睡眠状态（电源选项里关闭睡眠），可以减少触发概率                            │
  └────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

  ---
  关键信息速查

  肇事文件:   nipcibrd.sys
  发布者:     National Instruments
  崩溃类型:   DRIVER_POWER_STATE_FAILURE (0x9F)
  触发场景:   系统电源下降 (睡眠/休眠/关机)
  系统:       Windows 11 24H2 (Build 26100)
  运行时长:   5 天 2 小时

  这个崩溃发生在系统试图进入低功耗状态或关机时，NI PCI 桥驱动没有正确响应。如果你在使用 NI 的 DAQ、GPIB
  或其他测量设备，这就是根本原因。

✻ Sautéed for 32s</code></pre><blockquote>
<p>[!NOTE]</p>
<p>不要用指令直接卸载 NI 系列的驱动口牙！！！会把系统干爆的，已严肃重装 windows</p>
<p>要用 NI-VISA manager 卸载口牙！！！</p>
</blockquote>

          <p style='text-align: right'>
          <a href='https://www.yono233.cn/posts/novel/26_7_31_ni-win#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164999773374386176</guid>
  <category>post</category>
<category>禹步</category>
 </item>
  <item>
    <title>无 Docker Hub 环境下容器化部署 Mihomo 代理</title>
    <link>https://www.yono233.cn/posts/HuangShiGong/docker-hub-mihomo</link>
    <pubDate>Mon, 27 Jul 2026 08:52:21 GMT</pubDate>
    <description>决定新开一个分类&quot;天书&quot;，url-slug 为 &quot;黄石公&quot;
用于存放完全 AI 生成的教程内容
这就</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/posts/HuangShiGong/docker-hub-mihomo'>https://www.yono233.cn/posts/HuangShiGong/docker-hub-mihomo</a></blockquote>
          <blockquote>
<p>决定新开一个分类&quot;天书&quot;，url-slug 为 &quot;黄石公&quot;</p>
<p>用于存放完全 AI 生成的教程内容</p>
<p>这就是第一篇了</p>
</blockquote>
<h1>无 Docker Hub 环境下容器化部署 Mihomo 代理</h1>
<h2>适用场景</h2>
<p>服务器几乎不能正常访问外网，Docker Hub 不可用（或无法拉取任何镜像）。Podman、宝塔面板、或直接安装的 Docker Engine 均适用。</p>
<h2>核心思路</h2>
<ul>
<li>利用服务器已有的任意 Alpine 镜像（如 <code>redis:alpine</code>）作为构建时的 CA 证书来源，最终镜像基于 <code>scratch</code> 构建，<strong>不产生额外镜像拉取</strong>。</li>
<li>所有新增文件集中在 <code>/root/mihomo/</code> 目录下，不污染服务器其他位置。</li>
<li>仅目标容器走代理，宿主机与其他容器不受影响。</li>
</ul>
<hr>
<h2>第一步：准备文件</h2>
<p>以下文件可在**个人 PC（能正常访问 GitHub）**上下载好，再放置到服务器的 <code>/root/mihomo/</code> 目录。</p>
<table>
<thead>
<tr>
<th>文件</th>
<th>作用</th>
<th>获取方式</th>
</tr>
</thead>
<tbody><tr>
<td><code>mihomo</code>（二进制）</td>
<td>代理核心</td>
<td><a href="https://github.com/MetaCubeX/mihomo/releases">GitHub Releases</a> 下载 <code>mihomo-linux-amd64-v*.gz</code>，解压后重命名为 <code>mihomo</code></td>
</tr>
<tr>
<td><code>geoip.metadb</code></td>
<td>GeoIP 规则数据库</td>
<td><a href="https://github.com/MetaCubeX/meta-rules-dat/releases">meta-rules-dat Releases</a> 下载 <code>geoip.metadb</code>，放入 <code>data/</code> 子目录</td>
</tr>
<tr>
<td><code>config.yaml</code></td>
<td>代理配置</td>
<td>见下方</td>
</tr>
<tr>
<td><code>Dockerfile</code></td>
<td>镜像构建</td>
<td>见下方</td>
</tr>
<tr>
<td><code>docker-compose.yml</code></td>
<td>容器编排</td>
<td>见下方</td>
</tr>
</tbody></table>
<p>最终目录结构：</p>
<pre><code class="language-">/root/mihomo/
├── mihomo              # 二进制文件
├── config.yaml         # 代理配置
├── Dockerfile
├── docker-compose.yml
└── data/
    └── geoip.metadb    # GeoIP 数据库</code></pre><hr>
<h2>第二步：config.yaml</h2>
<p>核心思路：订阅 URL 返回完整配置，从中提取 DNS 和 rules 部分直接放入 config.yaml；代理节点通过 <code>proxy-providers</code> 指向订阅 URL，由 mihomo 每日自动拉取更新。</p>
<p>有一些部分依据服务商不同，填写服务商给到的订阅 URL 中的对应固定部分，<strong>可以由 AI 代劳</strong>。</p>
<blockquote>
<p><code>url:</code> 填入你的订阅地址，URL 本身自带鉴权（路径中的随机 token），无需额外认证。</p>
</blockquote>
<pre><code class="language-yaml"># 端口与模式
port: 7890
socks-port: 7891
allow-lan: true
mode: Rule
log-level: info
external-controller: 0.0.0.0:9090

# DNS（从订阅返回内容中提取）
dns:
  enable: true
  # ... 完整内容从订阅 URL 拉取后填入

# 每日自动从订阅地址拉取最新节点
proxy-providers:
  sub:
    type: http
    url: "https://你的订阅地址?clash=3"
    interval: 86400
    path: ./providers/sub.yaml
    health-check:
      enable: true
      url: https://cp.cloudflare.com
      interval: 300

# 自动选择最快节点
proxy-groups:
  - name: 🔰 选择节点
    type: url-test
    use: [sub]
    url: https://www.gstatic.com/generate_204
    interval: 300

# 规则（从订阅返回内容中提取）
rules:
  - GEOIP,CN,DIRECT
  - MATCH,🔰 选择节点</code></pre><hr>
<h2>第三步：Dockerfile</h2>
<pre><code class="language-dockerfile">FROM redis:alpine AS certs
FROM scratch
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY mihomo /mihomo
ENTRYPOINT ["/mihomo"]
CMD ["-d", "/etc/mihomo"]</code></pre><ul>
<li>第一阶段 <code>FROM redis:alpine</code>：使用服务器已有镜像，仅提取 CA 根证书。</li>
<li>第二阶段 <code>FROM scratch</code>：空镜像，最终产物仅包含 mihomo 二进制与 CA 证书，干净无冗余。</li>
<li><code>redis:alpine</code> 仅作为构件引用，不运行、不被修改，可替换为服务器上任意 Alpine 系镜像。</li>
</ul>
<hr>
<h2>第四步：docker-compose.yml</h2>
<pre><code class="language-yaml">services:
  mihomo:
    build: .
    container_name: mihomo
    restart: unless-stopped
    volumes:
      - ./config.yaml:/etc/mihomo/config.yaml:ro
      - ./data:/etc/mihomo
    ports:
      - "7890:7890"
      - "9090:9090"</code></pre><ul>
<li><code>build: .</code> 使用本地 Dockerfile 构建，无需拉取外部镜像。</li>
<li><code>9090</code> 端口为 REST API（可选，用于热更新或状态查询）。</li>
</ul>
<hr>
<h2>第五步：构建与启动</h2>
<pre><code class="language-bash">cd /root/mihomo

# 确保二进制文件存在且可执行
chmod +x mihomo

# 构建并启动
docker compose build
docker compose up -d</code></pre><p>验证代理可用：</p>
<pre><code class="language-bash">curl -x http://localhost:7890 https://www.google.com -o /dev/null -w "%{http_code}"</code></pre><p>返回 <code>200</code> 即成功。</p>
<hr>
<h2>第六步：让其他容器走代理</h2>
<p>以博客后端容器为例，在其 <code>docker-compose.yml</code> 中添加环境变量：</p>
<pre><code class="language-yaml">environment:
  HTTP_PROXY: "http://172.17.0.1:7890"
  HTTPS_PROXY: "http://172.17.0.1:7890"
  NO_PROXY: "localhost,127.0.0.1"</code></pre><p><code>172.17.0.1</code> 为 Docker 默认 bridge 网关，容器通过它访问宿主机上 mihomo 映射的 <code>7890</code> 端口。如后端使用自定义网络，用 <code>docker inspect &lt;容器名&gt; | grep Gateway</code> 确认实际网关地址。</p>
<p>重建目标容器后进入容器验证：</p>
<pre><code class="language-bash">docker exec &lt;容器名&gt; curl -x http://172.17.0.1:7890 https://www.google.com -o /dev/null -w "%{http_code}"</code></pre><p>返回 <code>200</code> 即完成。</p>
<hr>
<h1>对于 mx-space 的 OAuth</h1>
<p>mx-space 使用 better-auth 包进行 OAuth 认证，但这个包不会默认走容器的代理。所以需要 js 注入的额外处理。</p>
<h3>问题</h3>
<p>设置了 <code>HTTP_PROXY</code> 环境变量后，<code>curl</code> 等传统工具能走代理，但 better-auth、Google OAuth 等使用了 Node.js 原生 <code>fetch()</code> 的包仍然直连——因为 <code>fetch()</code> 底层是 <code>undici</code>，它<strong>不读取 <code>HTTP_PROXY</code> 环境变量</strong>。</p>
<h3>解决方案</h3>
<p>三步：注入脚本 → 声明依赖 → 部署。</p>
<h4>1. 创建 <code>proxy-patch.mjs</code></h4>
<pre><code class="language-js">import { ProxyAgent, setGlobalDispatcher } from 'undici';

const proxy = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
if (proxy) {
  setGlobalDispatcher(new ProxyAgent(proxy));
}</code></pre><h4>2. 容器 docker-compose.yml 中添加</h4>
<pre><code class="language-yaml">environment:
  NODE_OPTIONS: "--import /app/proxy-patch.mjs"
  # HTTP_PROXY 和 HTTPS_PROXY 已有

volumes:
  - ./proxy-patch.mjs:/app/proxy-patch.mjs:ro
  - ./undici:/app/node_modules/undici:ro   # 如果镜像内未预装 undici</code></pre><h4>3. 如果镜像内未预装 undici</h4>
<p>在个人 PC 上下载 undici npm 包，放入容器项目目录：</p>
<pre><code class="language-bash"># 在 PC 上（需要 npm）
npm pack undici --pack-destination .
tar -xzf undici-*.tgz
mv package undici</code></pre><p>将 <code>undici/</code> 目录随容器项目一起传到服务器即可。</p>
<p>最终的结构类似</p>
<pre><code class="language-">  mx-core/
  ├── docker-compose.yml
  ├── proxy-patch.mjs
  ├── undici/              # undici 8.9.0 完整包
  └── data/</code></pre><hr>
<h3>验证</h3>
<p>进入容器后执行：</p>
<pre><code class="language-bash">node -e "fetch('https://www.google.com').then(r =&gt; console.log(r.status))"</code></pre><p>返回 <code>200</code> 即代理生效。</p>

          <p style='text-align: right'>
          <a href='https://www.yono233.cn/posts/HuangShiGong/docker-hub-mihomo#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">163570901592313856</guid>
  <category>post</category>
<category>天书</category>
 </item>
  <item>
    <title>GD32H7 的 MPU 备查——关于 0x0000 寻址区的配置</title>
    <link>https://www.yono233.cn/notes/55</link>
    <pubDate>Fri, 10 Jul 2026 06:39:50 GMT</pubDate>
    <description>旧的背景故事

之前有过 GD32H7 打开网络功能的尝试，因为没有在最终工程产品中用到该芯片，仅作</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/notes/55'>https://www.yono233.cn/notes/55</a></blockquote>
          <h1>旧的背景故事</h1>
<p>之前有过 GD32H7 打开网络功能的尝试，因为没有在最终工程产品中用到该芯片，仅作评估后封存。</p>
<p>当时的现象是，完全采用与官方例程一模一样的<strong>库、外设配置、调用顺序、操作方法</strong>，只是我将官方的 keil 例程改为 cmake-gcc 工程。问题是<strong>官方例程编译烧录后一切正常，而我的工程编译后无论如何都无法 ping 通</strong>。</p>
<p>我对 lwip 这个网络库、ETH 外设这块的了解实在有限，GD 支持也无法找到原因所在。</p>
<h1>原因</h1>
<p>总的来说，GD32H7 芯片需要以类似这样的配置方法，将 0x0000 开始的整个寻址区域，都设置 MPU 保护，作为背景设置，然后后续的 MPU 配置块再做实际配置。</p>
<pre><code class="language-c">    mpu_region_init_struct mpu_init_struct;
    /* disable MPU */
    ARM_MPU_Disable();
   
    mpu_init_struct.region_number       = MPU_REGION_NUMBER0;
    mpu_init_struct.region_base_address = 0x00000000U;
    mpu_init_struct.instruction_exec    = MPU_INSTRUCTION_EXEC_NOT_PERMIT;
    mpu_init_struct.access_permission   = MPU_AP_NO_ACCESS;
    mpu_init_struct.tex_type            = MPU_TEX_TYPE0;
    mpu_init_struct.access_shareable    = MPU_ACCESS_SHAREABLE;
    mpu_init_struct.access_cacheable    = MPU_ACCESS_NON_CACHEABLE;
    mpu_init_struct.access_bufferable   = MPU_ACCESS_NON_BUFFERABLE;
    mpu_init_struct.subregion_disable   = 0x87;
    mpu_init_struct.region_size         = MPU_REGION_SIZE_4GB;  
   
    mpu_region_config(&mpu_init_struct);
    mpu_region_enable();
    /* enable MPU */
    ARM_MPU_Enable(MPU_MODE_PRIV_DEFAULT);</code></pre><h1>问题是如何产生的</h1>
<p>在 GD32H7 手册<strong>1.3 存储器映射</strong>上可见</p>
<p>0x0000 0000~0x0007 FFFF 划归为 ITCM RAM(来自共享 RAM)，但是这个区域又被划归为<strong>代码</strong>区域。我暂时不太理解其意图。总之就是在内核运行的过程中将该区域认为是 RAM 区，所以有读写可能。</p>
<p>依据参考材料的说法，GD 官方勘误表或任何手册都没有描述这一点。当然官方例程很多时候也没有做到这一点，否则我此前改为 gcc 编译应当是一切正常。只有 ST 官方的甚至 <a href="https://support.touchgfx.com/zh-CN/docs/development/scenarios/running-graphics-on-stm32h7r">touchgfx</a> 这个图形库手册而非芯片手册中有这样的描述。</p>
<blockquote>
<p>M7处理器上，必须防止对外部存储器的推测性访问，否则可能引发高延迟或系统错误。 对于STM32H7R/S，这将影响访问内存的AXI主设备，并显著降低图形性能。
MPU可通过控制可访问的地址范围来防止推测性读取访问。 最简单的方法是使用包含整个内存区域的背景区域，通过将其设置为“强排序，永不执行”来限制访问。
背景区域应定义在默认区域ID-1中，因为所有其他区域的优先级都将高于此区域。 然后，应为需要访问的内存区域定义具有相应设置的其他MPU区域。 在STM32H7R/S上最多可定义16个区域。</p>
</blockquote>
<p>也就是这是一个 M7 内核原设计就有的缺陷，而我使用 stm32H7 只是 ST 官方做成芯片时使用某种手段规避了问题，或者甚至只是恰巧没有碰到。</p>
<h1>参考资料备查</h1>
<p>GD32H7移植NEXTDUO问题</p>
<p><a href="https://forum.anfulai.cn/forum.php?mod=viewthread&tid=125782&extra=">https://forum.anfulai.cn/forum.php?mod=viewthread&amp;tid=125782&amp;extra=</a></p>
<p> GD32H757ZMT6 仅因 Flash/Load image 布局变化，程序运行稳定性就出现明显差异</p>
<p><a href="https://forum.anfulai.cn/forum.php?mod=viewthread&tid=131926&extra=">https://forum.anfulai.cn/forum.php?mod=viewthread&amp;tid=131926&amp;extra=</a></p>

          <p style='text-align: right'>
          <a href='https://www.yono233.cn/notes/55#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">161317383669563441</guid>
  <category>note</category>
false
 </item>
  <item>
    <title>md 的迷思</title>
    <link>https://www.yono233.cn/posts/novel/26_6_17_md</link>
    <pubDate>Wed, 17 Jun 2026 07:10:38 GMT</pubDate>
    <description>markdown

实际上在部署本博客前，我从来没有接触过 Markdown(md) 标记语法。Mi</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/posts/novel/26_6_17_md'>https://www.yono233.cn/posts/novel/26_6_17_md</a></blockquote>
          <h1>markdown</h1>
<p>实际上在部署本博客前，我从来没有接触过 Markdown(md) 标记语法。<a href="https://mx-space.js.org/">Mix Space</a> 博客框架算是我的 md 入门。</p>
<p>在后续尝试搭出 wiki、搭出网站、md 产出说明书等等实用实例时，md 都可以作为关键的<strong>内容源</strong>，而且非常便于使用 git 之类做版本控制和更改比对。</p>
<p>恰巧，在我对 md 的深入使用的时间，正好贴合，AI 领域也从早期的“提示词”对话，变成了现在“skill  +agent”作为真正助手。现在想要真正用好 AI，md 语言和文件已经是必不可少了。</p>
<p>我和领导一直有一个巨大分歧，我认为“内容很重要，形式更重要”，而他认为形式根本不用管。</p>
<p>无论什么内容，最终是给某个“实体”阅读和理解，而合理的形式将极大增强可读性，不好的形式容易曲解内容。</p>
<p>md 恰好<strong>编写内容</strong>、<strong>调整格式</strong>都还算简单，也适合人类、AI、或者任何其他脚本工具理解。在现代，可能所有的内容写作都应该是 md 格式，再制作工具或借助 AI 转为目标形式。</p>
<h1>agent</h1>
<p>经历<strong>直接调用大模型</strong>[^直接大模型] &gt;&gt;&gt;<strong>compound AI system</strong>[^复合式AI系统]，现在特别是龙虾爆火之后，各种真正的 agent<a href>^智能体</a> 加速涌现。Claude code、GitHub copilot 都开发成了真正 agent 的形式。</p>
<p>[^直接大模型]: 一段 prompt 丢给模型,模型直接吐出一段文本</p>
<p>[^复合式AI系统]: 通常是对话框形式，他不能输入一个长期目标，不断迭代完成，但是具有一定预处理和调用工具的能力，也不用自己输入各种各样 prompt</p>
<p>在 agent 的使用中，md 的地位更加重要</p>
<ul>
<li>作为技能提示，指示 agent 工作时的关键侧重技术栈</li>
<li>作为边界条件，设定 agent 可自主操作的文件边界，哪些操作一定需要获得批准</li>
<li>作为风格侧重，更注重性能，还是更注重封装和代码可重用性</li>
<li>作为可信信源，增加一些绝对可信的资料信息供 agent 查阅</li>
<li>作为持久性信息，让 AI 缩减上下文、记录项目现状、项目特定注重项，跨项目阶段和环境做统一</li>
</ul>
<p>无论如何，哪怕你创建一个 skill.md 像直接用大模型一样写一句，也是会产生效果的。</p>
<pre><code class="language-">**你是一个xxx技术专家,擅长xxxx领域应用**</code></pre><h1>我的 agent 学习</h1>
<p>起因并不是为了学习 agent，市面上的<strong>酒馆AI</strong>调用的模型太烂了，根本没办法正常游玩，迫不得已自己部署一个来玩，体验相当不错。</p>
<p><a href="https://github.com/SillyTavern/SillyTavern">SillyTavern/SillyTavern: LLM Frontend for Power Users.</a></p>
<p>为了自己好的游戏体验，自己编写<strong>角色/世界卡</strong>，但是我的想法又过分复杂，不得已自己部署了一个 agent 来辅助创作。</p>
<p>抄袭自该项目，借助 claude 做了一些微调。</p>
<p><a href="https://github.com/CodePothunter/lingque">CodePothunter/lingque: A self-awared agent(s) assistents with very compact core, natively connected to Lark (feishu).</a></p>
<p>这个项目相对简单，各种机制却非常合我的胃口。具体机制可见项目开发文档，或者作者在抄袭项目下的声明(这部分写的比原项目文档更加清晰)。并且我计划基于该项目制作所有自用的 agent。</p>
<p><a href="https://github.com/LDPrompt/lingque/issues/1">关于 LDPrompt/lingque 项目抄袭灵雀（LingQue）框架的声明 · Issue #1 · LDPrompt/lingque</a></p>
<p>在该项目中，也是使用 SOUL.md/MEMORY.md 做总体技能设定/持久化，同时他也支持本地优先的知识库，知识文档也应该以 md 格式存放便于 agent 理解。</p>
<h1>文字游戏再次崛起</h1>
<p>现在 AI 还依然无法便宜地接入现代化的游戏中，但是已经在老派类型<strong>文字游戏</strong>中大放异彩，非常痴迷文字游戏的我已经爽到。</p>
<p>这些所有关于 AI 的用户可设定部分，依然是 md 格式。我想未来模型便宜到足够接入现代化游戏的时候，也是如此。</p>
<p>AI 让 md 再次伟大？</p>

          <p style='text-align: right'>
          <a href='https://www.yono233.cn/posts/novel/26_6_17_md#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">161317383661174830</guid>
  <category>post</category>
<category>禹步</category>
 </item>
  <item>
    <title>FlashDB 的引入</title>
    <link>https://www.yono233.cn/posts/novel/flashdb</link>
    <pubDate>Wed, 13 May 2026 04:04:30 GMT</pubDate>
    <description>前置条件

这个简单数据库可以以任意存储介质作为实际空间。

对于 flash，通常的最小擦除单位是</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/posts/novel/flashdb'>https://www.yono233.cn/posts/novel/flashdb</a></blockquote>
          <h1>前置条件</h1>
<p>这个简单数据库可以以任意存储介质作为实际空间。</p>
<p>对于 flash，通常的最小擦除单位是扇区。</p>
<p>如果是片上 flash，至少需要规划预留一个扇区对齐的区域供 FlashDB 使用。</p>
<p>如果是外部 flash，除了扇区对齐的规划，需要实现并测试<code>读、写、擦除</code>的 API 作为准备工作。</p>
<p>如果是 eeprom，同样的，实现并测试<code>读、写、擦除</code>的 API 作为准备工作，此时会简单很多，不必考虑扇区对齐问题。擦除 API 是为了兼容 FlashDB 默认存储介质是 flash，采用符合 flash 的行为逻辑，实际上可以将擦除 API 做空操作。</p>
<p>FlashDB 会自行处理好扇区对齐问题，所以在实现 API 时不必添加扇区对齐的检查，以减小开销。</p>
<h1>引入源码包</h1>
<p>在 <a href="https://github.com/armink/FlashDB">armink/FlashDB</a> 可以下载到原始源码包。</p>
<p>但是他没有 CMakeLists，需要了解其源码划分，才能正确引入所需的源码。</p>
<p>其实际上必须引入的源码文件夹如下，其他的则可以在工程中删除。下面用更清晰的 ASCII 树展示，方便在 Markdown 中阅读和复制。</p>
<pre><code class="language-">FlashDB/
├─ demos/
├─ docs/
├─ inc/            # 需要加入到 include path
├─ port/
│  └─ fal/
│     ├─ docs/
│     ├─ inc/      # 需要加入到 include path
│     ├─ samples/
│     └─ src/      # 需要加入到编译的 .c 文件
├─ samples/
├─ src/            # 需要加入到编译的 .c 文件
├─ tests/
└─ zephyr/</code></pre><p>其中标识的 .c 文件需要加入编译，同时加入以下的 includePATH</p>
<pre><code class="language-">FlashDB/inc
FlashDB/port/fal/inc</code></pre><p>FlashDB/inc 中有 <code>fdb_cfg_template.h</code>，复制一份并重命名为 <code>fdb_cfg.h</code> 放在自己的工程中，比如原地，在 includePATH 范围内就好。</p>
<p>最小起步时其中的默认配置不必修改，只要考虑编写 <code>FDB_PRINT</code> 即可，例如我编写为使用 RTT 打印，或者留空。</p>
<pre><code class="language-c">#define FDB_PRINT(...) SEGGER_RTT_printf(0, __VA_ARGS__)

// #define FDB_PRINT(...) \
//     do                 \
//     {                  \
//     }                  \
//     while(0)</code></pre><p>新建一份 <code>fal_cfg.h</code> 放在自己的工程中，例如</p>
<pre><code class="language-c">#ifndef _FAL_CFG_H_
#define _FAL_CFG_H_

#define FAL_PART_HAS_TABLE_CFG

#define FAL_FLASHDB_DEV_NAME       "norflash0"
#define FAL_FLASHDB_KV_PART_NAME   "fdb_kv_cfg"
#define FAL_FLASHDB_KV_PART_OFFSET (0x00040000u)
#define FAL_FLASHDB_KV_PART_LENGTH (0x00040000u)

extern struct fal_flash_dev nor_flash0;

#define FAL_FLASH_DEV_TABLE \
    {                       \
        &nor_flash0,        \
    }

#ifdef FAL_PART_HAS_TABLE_CFG
#define FAL_PART_TABLE                                                                                                                    \
    {                                                                                                                                     \
        {FAL_PART_MAGIC_WORD, FAL_FLASHDB_KV_PART_NAME, FAL_FLASHDB_DEV_NAME, FAL_FLASHDB_KV_PART_OFFSET, FAL_FLASHDB_KV_PART_LENGTH, 0}, \
    }
#endif

#endif /* _FAL_CFG_H_ */</code></pre><h1>为工程引入 fal</h1>
<p>注意到上面的 <code>fal_cfg.h</code> 其中有这样的内容，其中 <code>FAL_FLASH_DEV_TABLE</code> 是一个必须定义的 falsh 设备表，这里有一个 <code>nor_flash0</code> 设备描述对象。</p>
<p>这些描述对象与期望操作的 flash 抽象一一对应，比如我这里只有一个 spi-norflash，所以只使用了一个抽象对象。</p>
<pre><code class="language-c">extern struct fal_flash_dev nor_flash0;

#define FAL_FLASH_DEV_TABLE \
    {                       \
        &nor_flash0,        \
    }</code></pre><p>在现在，虽然使用了声明，但是这个 flash 抽象依然还没有定义，定义这个抽象内容是使其真正具备真实 flash 操作能力的关键。</p>
<p>这里我们可以新建一个源码进行整体绑定，使其具有 读/写/擦 接口。比如如下的核心定义，其中</p>
<ul>
<li><code>.len</code> 是完整 flash 空间大小，</li>
<li><code>.blk_size</code> 是擦除最小单位在 flash 中通常是扇区大小。</li>
<li><code>.ops</code> 是 读/写/擦 回调，在回调中使用<strong>前置条件</strong>中已实现的 API。</li>
<li><code>.write_gran</code> 是写最小字节数粒度，例如 norflash  此值定义为 1，而片上 flash 可能要求 4/8 字节对齐。</li>
</ul>
<pre><code class="language-c">struct fal_flash_dev nor_flash0 = {
    .name       = FAL_FLASHDB_DEV_NAME,
    .addr       = 0,
    .len        = 1 * 1024 * 1024,
    .blk_size   = DEVICE_SECTOR_SIZE,
    .ops        = {fal_norflash_init,
                     fal_norflash_read, 
                     fal_norflash_write, 
                     fal_norflash_erase},
    .write_gran = FDB_WRITE_GRAN,
};</code></pre><p>下面会展示为了支持这个抽象内容的完整源码，在这里有一个重要的问题需要处理。因为 fal 似乎并没有按页逐步写的操作，所以在 norflash 的回调处理中，需要注意不要跨页写，分为多次写使能和写请求。</p>
<pre><code class="language-c">#include &lt;NorFlashSpi.h&gt;
/* Port */
extern uint32_t FlashSpiTransmitPort(_NOR_FLASH_MESSAGE *pd, const uint8_t *TBuf, uint32_t ui32Len);
extern uint32_t FlashSpiReceivePort(_NOR_FLASH_MESSAGE *pd, uint8_t *RBuf, uint32_t ui32Len);
extern uint32_t FlashSpiTransmitReceivePort(_NOR_FLASH_MESSAGE *pd, const uint8_t *TBuf, uint8_t *RBuf, uint32_t ui32Len);
#include &lt;fal.h&gt;
#include &lt;fdb_cfg.h&gt;
#include &lt;includes.h&gt;

extern volatile uint8_t fSpiTxBuf[];
extern volatile uint8_t fSpiRxBuf[];
/* 片选使能函数 */
#define CS_EN( ) HAL_GPIO_WritePin(SPI5_FLASH_CS_GPIO_Port, SPI5_FLASH_CS_Pin, GPIO_PIN_RESET);
/* 片选失能函数 */
#define CS_UN( ) HAL_GPIO_WritePin(SPI5_FLASH_CS_GPIO_Port, SPI5_FLASH_CS_Pin, GPIO_PIN_SET);
/* 定义写入最小单位 */
#define FLASH_PAGE_SIZE   256

#define FALFDB_TRACE(...) SEGGER_RTT_printf(0, "[FALNOR] " __VA_ARGS__)

static int      fal_norflash_init(void);
static int      fal_norflash_read(long offset, uint8_t *buf, size_t size);
static int      fal_norflash_write(long offset, const uint8_t *buf, size_t size);
static int      fal_norflash_erase(long offset, size_t size);

static uint32_t fal_norflash_wait_idle(void)
{
    uint32_t i;

    fSpiTxBuf[0] = CMD_RDSR1;
    fSpiTxBuf[1] = 0;

    CS_EN( );
    if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, 2) != RETURN_DEFAULT)
    {
        CS_UN( );
        return RETURN_FLASH_READ_ERR;
    }
    CS_UN( );

    i = 0;
    while(((fSpiRxBuf[1] & WIP_FLAG) != 0) && (i &lt; 0xFFFF))
    {
        CS_EN( );
        if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, 2) != RETURN_DEFAULT)
        {
            CS_UN( );
            return RETURN_FLASH_READ_ERR;
        }
        CS_UN( );
        i++;
    }

    if((fSpiRxBuf[1] & WIP_FLAG) != 0)
    {
        return RETURN_FLASH_BUSY;
    }

    return RETURN_DEFAULT;
}

struct fal_flash_dev nor_flash0 = {
    .name       = FAL_FLASHDB_DEV_NAME,
    .addr       = 0,
    .len        = 1 * 1024 * 1024,
    .blk_size   = DEVICE_SECTOR_SIZE,
    .ops        = {fal_norflash_init, fal_norflash_read, fal_norflash_write, fal_norflash_erase},
    .write_gran = FDB_WRITE_GRAN,
};

static int fal_norflash_init(void)
{
    /* 发送buffer转为 读ID指令 */
    fSpiTxBuf[0] = CMD_RDID;
    fSpiTxBuf[1] = 0;
    fSpiTxBuf[2] = 0;
    fSpiTxBuf[3] = 0;
    fSpiRxBuf[1] = 0;
    fSpiRxBuf[2] = 0;
    fSpiRxBuf[3] = 0;

    CS_EN( );
    if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, 4) != RETURN_DEFAULT) // 收发
    {
        FALFDB_TRACE("[SPI-FLASH]init warn: read id transfer failed\r\n");
    }
    CS_UN( );

    uint32_t ChipID = ((uint32_t)fSpiRxBuf[1] &lt;&lt; 16) | ((uint32_t)fSpiRxBuf[2] &lt;&lt; 8) | fSpiRxBuf[3];
    switch(ChipID)
    {
    case W25Q80_ID:
        nor_flash0.len      = 1 * 1024 * 1024;
        nor_flash0.blk_size = 4 * 1024;
        break;
    case W25Q64_ID:
        nor_flash0.len      = 8 * 1024 * 1024;
        nor_flash0.blk_size = 4 * 1024;
        break;
    case W25Q128_ID:
        nor_flash0.len      = 16 * 1024 * 1024;
        nor_flash0.blk_size = 4 * 1024;
        break;
    case W25Q256_ID:
        nor_flash0.len      = 32 * 1024 * 1024;
        nor_flash0.blk_size = 4 * 1024;
        break;
    case W25Q512_ID:
        nor_flash0.len      = 64 * 1024 * 1024;
        nor_flash0.blk_size = 4 * 1024;
        break;
    default:
        nor_flash0.len      = 0;
        nor_flash0.blk_size = 0;
        FALFDB_TRACE("[SPI-FLASH]init failed: unsupported chip id=0x%08X\r\n", (unsigned)ChipID);
        return -1;
    }

    return 0;
}

static int fal_norflash_read(long offset, uint8_t *buf, size_t size)
{
    uint32_t addr;
    uint32_t chunk;
    size_t   remaining;
    size_t   copied;

    if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
    {
        FALFDB_TRACE("read failed: flash busy before read\r\n");
        return -1;
    }

    addr = (uint32_t)offset;

    /* 发送读指令和3字节地址 */
    fSpiTxBuf[0] = CMD_READ;
    fSpiTxBuf[1] = (addr &gt;&gt; 16) & 0xFF;
    fSpiTxBuf[2] = (addr &gt;&gt; 8) & 0xFF;
    fSpiTxBuf[3] = addr & 0xFF;

    CS_EN( );
    if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 4) != RETURN_DEFAULT)
    {
        CS_UN( );
        FALFDB_TRACE("read failed: cmd tx error off=0x%08X size=%u\r\n", (unsigned)offset, (unsigned)size);
        return -1;
    }

    remaining = size;
    copied    = 0;
    while(remaining &gt; 0)
    {
        chunk = (remaining &gt; DEVICE_SECTOR_SIZE) ? DEVICE_SECTOR_SIZE : (uint32_t)remaining;
        if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, chunk) != RETURN_DEFAULT)
        {
            CS_UN( );
            FALFDB_TRACE("read failed: data rx error off=0x%08X remain=%u\r\n", (unsigned)(addr + copied), (unsigned)remaining);
            return -1;
        }

        for(uint32_t i = 0; i &lt; chunk; i++)
        {
            buf[copied + i] = fSpiRxBuf[i];
        }

        copied += chunk;
        remaining -= chunk;
    }

    CS_UN( );

    return (int)size;
}

static int fal_norflash_write(long offset, const uint8_t *buf, size_t size)
{
    uint32_t addr;
    size_t   remaining;
    size_t   copied;

    if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
    {
        FALFDB_TRACE("write failed: flash busy before write\r\n");
        return -1;
    }

    addr      = (uint32_t)offset;
    remaining = size;
    copied    = 0;

    while(remaining &gt; 0)
    {
        uint32_t page_offset = addr % FLASH_PAGE_SIZE;
        uint32_t page_room   = FLASH_PAGE_SIZE - page_offset;
        uint32_t prog_len    = (remaining &gt; page_room) ? page_room : (uint32_t)remaining;

        /* 写使能 */
        fSpiTxBuf[0] = CMD_WREN;
        CS_EN( );
        if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 1) != RETURN_DEFAULT)
        {
            CS_UN( );
            FALFDB_TRACE("write failed: wren tx error off=0x%08X\r\n", (unsigned)addr);
            return -1;
        }
        CS_UN( );

        /* 页编程命令 + 3字节地址 + 数据 */
        fSpiTxBuf[0] = CMD_PP;
        fSpiTxBuf[1] = (addr &gt;&gt; 16) & 0xFF;
        fSpiTxBuf[2] = (addr &gt;&gt; 8) & 0xFF;
        fSpiTxBuf[3] = addr & 0xFF;
        for(uint32_t i = 0; i &lt; prog_len; i++)
        {
            fSpiTxBuf[4 + i] = buf[copied + i];
        }

        CS_EN( );
        if(FlashSpiTransmitReceivePort(NULL, (uint8_t *)fSpiTxBuf, (uint8_t *)fSpiRxBuf, 4 + prog_len) != RETURN_DEFAULT)
        {
            CS_UN( );
            FALFDB_TRACE("write failed: pp tx error off=0x%08X size=%u\r\n", (unsigned)addr, (unsigned)prog_len);
            return -1;
        }
        CS_UN( );

        if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
        {
            FALFDB_TRACE("write failed: wait idle timeout off=0x%08X\r\n", (unsigned)addr);
            return -1;
        }

        addr += prog_len;
        copied += prog_len;
        remaining -= prog_len;
    }

    /* 关闭写使能 */
    fSpiTxBuf[0] = CMD_DISWR;
    CS_EN( );
    if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 1) != RETURN_DEFAULT)
    {
        CS_UN( );
        FALFDB_TRACE("write warn: diswr tx error\r\n");
        return -1;
    }
    CS_UN( );

    if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
    {
        FALFDB_TRACE("write failed: flash busy after diswr\r\n");
        return -1;
    }

    return (int)size;
}

static int fal_norflash_erase(long offset, size_t size)
{
    uint32_t addr;
    uint32_t blk_size;
    size_t   remaining;

    blk_size = nor_flash0.blk_size;

    if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
    {
        FALFDB_TRACE("erase failed: flash busy before erase\r\n");
        return -1;
    }

    addr      = (uint32_t)offset;
    remaining = size;

    while(remaining &gt; 0)
    {
        /* 写使能 */
        fSpiTxBuf[0] = CMD_WREN;
        CS_EN( );
        if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 1) != RETURN_DEFAULT)
        {
            CS_UN( );
            FALFDB_TRACE("erase failed: wren tx error off=0x%08X\r\n", (unsigned)addr);
            return -1;
        }
        CS_UN( );

        /* 扇区擦除命令 + 3字节地址 */
        fSpiTxBuf[0] = CMD_SE;
        fSpiTxBuf[1] = (addr &gt;&gt; 16) & 0xFF;
        fSpiTxBuf[2] = (addr &gt;&gt; 8) & 0xFF;
        fSpiTxBuf[3] = addr & 0xFF;

        CS_EN( );
        if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 4) != RETURN_DEFAULT)
        {
            CS_UN( );
            FALFDB_TRACE("erase failed: se tx error off=0x%08X\r\n", (unsigned)addr);
            return -1;
        }
        CS_UN( );

        if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
        {
            FALFDB_TRACE("erase failed: wait idle timeout off=0x%08X\r\n", (unsigned)addr);
            return -1;
        }

        addr += blk_size;
        remaining -= blk_size;
    }

    /* 关闭写使能 */
    fSpiTxBuf[0] = CMD_DISWR;
    CS_EN( );
    if(FlashSpiTransmitPort(NULL, (uint8_t *)fSpiTxBuf, 1) != RETURN_DEFAULT)
    {
        CS_UN( );
        FALFDB_TRACE("erase warn: diswr tx error\r\n");
        return -1;
    }
    CS_UN( );

    if(fal_norflash_wait_idle( ) != RETURN_DEFAULT)
    {
        FALFDB_TRACE("erase failed: flash busy after diswr\r\n");
        return -1;
    }

    return (int)size;
}</code></pre><h1>为工程引入 FlashDB</h1>
<p>有了此前由下至上的引入和支持，现在可以正式处理 FlashDB 的操作流程了。</p>
<p>现在需要处理的主要就是按序初始化了，一个最小的可用初始化顺序为</p>
<pre><code class="language-c">struct fdb_kvdb g_flashdb_kvdb;

int32_t ModuleFlashDBInit(void)
{
    uint32_t  sec_size;

    memset(&g_flashdb_kvdb, 0, sizeof(g_flashdb_kvdb));

    sec_size = (4 * 1024); // 默认单扇区大小;
    fdb_kvdb_control(&g_flashdb_kvdb, FDB_KVDB_CTRL_SET_LOCK, (void *)module_flashdb_lock);
    fdb_kvdb_control(&g_flashdb_kvdb, FDB_KVDB_CTRL_SET_UNLOCK, (void *)module_flashdb_unlock);
    fdb_kvdb_control(&g_flashdb_kvdb, FDB_KVDB_CTRL_SET_SEC_SIZE, &sec_size);

    result = fdb_kvdb_init(&g_flashdb_kvdb, "cfg", FAL_FLASHDB_KV_PART_NAME, NULL, NULL);
    if(result != FDB_NO_ERR)
    {
        FLASHDB_TRACE("init failed: fdb_kvdb_init err=%d\r\n", (int)result);
        return -1;
    }

    return 0;
}</code></pre><p>此后就可以使用这样的原子操作以读写键值</p>
<pre><code class="language-c">    struct fdb_blob blob;
    uint8_t verifyIp[4]   = {192,168,1,100};

    fdb_kv_set_blob(&g_flashdb_kvdb, "lan_ip", fdb_blob_make(&blob, verifyIp, sizeof(verifyIp)));
    fdb_kv_get_blob(&g_flashdb_kvdb, "lan_ip", fdb_blob_make(&blob, verifyIp, sizeof(verifyIp)));</code></pre><h1>需要自己做的包装</h1>
<ol>
<li>依据你的工程框架，可以使用线程锁来额外包装一套 API，以简单支持跨线程的键值读写。</li>
<li>进阶的，在包装中可以额外增加一个读 API 传参，在未命中键时使用默认值返回。类似 esp32 的 Preferences 数据库，<code>int    canBaud   = prefs.getInt(&quot;canbtaud&quot;, 250000);</code>。</li>
<li>更加进阶的，由于 FlashDB 是一套阻塞操作、SPI 收发相当耗时，在线程中直接使用原子操作将导致毫秒级的阻塞，这是不太能接受的。可以采用独立线程和请求队列，专用于管理 FlashDB 的读写操作。在通常的场景，采用异步使得业务逻辑更通顺。而在必须立即读写并获值参与逻辑时，也可以直接调用原子操作。</li>
</ol>
<h1>键值写入示例</h1>
<p><strong>设定</strong>: 70个键值对，50扇区分区（每扇区4KB）。</p>
<p>单条KV ≈ 40字节（18头 + 15键名 + 4值），每扇区可用4082字节，约存100条。</p>
<hr>
<h2>首次不断写入</h2>
<p>70个新键值顺序追加到扇区0。</p>
<pre><code class="language-">扇区0: [头][KV001][KV002]...[KV070]___________________
        ←─────── 2800字节 ────────→←── 剩余1282字节 ──→
扇区1~49: 全部 EMPTY</code></pre><p><strong>结果</strong>: 1个 USING（剩余~32条空间），49个 EMPTY。</p>
<hr>
<h2>第一次写到最后一个扇区</h2>
<p>每轮更新70个键值 → 旧条目标记 DELETED，新条目追加到尾部。</p>
<p>连续多轮更新后，扇区逐个填满：</p>
<pre><code class="language-">扇区0:  [头][✗✗✗...垃圾...][✓✓✓...32条]              ← FULL
扇区1:  [头][✓✓✓...38条][✗✗✗...垃圾...][✓✓✓...32条]   ← FULL
扇区2:  [头][✓✓✓...38条][✗✗✗...垃圾...][✓✓✓...32条]   ← FULL
...
扇区48: [头][✗✗✗...垃圾...][✓✓✓...70条]______________ ← FULL
扇区49: [头][✓✓✓...当前有效数据...]___________________ ← USING</code></pre><p><strong>结果</strong>: 49个 FULL，1个 USING（当前），<strong>0个 EMPTY</strong>。再也无空扇区可写。</p>
<hr>
<h2>第一次写到最后一个扇区后，下一次写入</h2>
<p>此时再写一条：<code>alloc_kv()</code> 发现空扇区不足 → <strong>触发 GC</strong>。</p>
<p>GC 选中扇区0（最脏），扫描其中所有条目：</p>
<pre><code class="language-">扇区0扫描:
  KV✗(DELETED) → 丢弃
  KV✓(WRITE)   → move_kv() 追加到扇区49尾部
  ...

扫描完毕后: 扇区0 → format_sector() 擦除 → EMPTY</code></pre><p>GC 后：</p>
<pre><code class="language-">扇区0:  [头]_________________________________________   ← 刚回收, EMPTY
扇区1~48: 仍然是垃圾（后续 GC 逐个回收）
扇区49: [头][...之前的数据][从扇区0搬来的有效条目...]    ← USING</code></pre><p><strong>结果</strong>: 回收1个空扇区，写入继续。之后每轮更新消耗约1扇区 + GC回收1扇区，循环往复。50扇区仅需始终维持1个空扇区即可无限运行。</p>

          <p style='text-align: right'>
          <a href='https://www.yono233.cn/posts/novel/flashdb#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">161317383661174829</guid>
  <category>post</category>
<category>禹步</category>
 </item>
  <item>
    <title>C2000 的特殊内存排布</title>
    <link>https://www.yono233.cn/notes/54</link>
    <pubDate>Wed, 22 Apr 2026 06:11:26 GMT</pubDate>
    <description>C2000 DSP 是现代化蝎子粑粑独一份的 16 位字节，所以内存寻址时和通常的 CHAR_BIT</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/notes/54'>https://www.yono233.cn/notes/54</a></blockquote>
          <p>C2000 DSP 是现代化蝎子粑粑独一份的 16 位字节，所以内存寻址时和通常的 CHAR_BIT == 8 不同。</p>
<p>这在协议层到数据零拷贝强转时需要格外注意。过去处理过，今天遇到又忘了，得重新手测。记录一下吧。</p>
<table>
<thead>
<tr>
<th></th>
<th>U32</th>
<th>实际内存排布</th>
</tr>
</thead>
<tbody><tr>
<td>C2000</td>
<td>0x87654321</td>
<td>4321 8765</td>
</tr>
<tr>
<td>通常的8位小端</td>
<td>0x87654321</td>
<td>21 43 65 87</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th></th>
<th>浮点值</th>
<th>实际内存排布</th>
<th>内存排布对应U32</th>
</tr>
</thead>
<tbody><tr>
<td>C2000</td>
<td>123.456</td>
<td>E979 42F6</td>
<td>0x42F6E979</td>
</tr>
<tr>
<td>通常的8位小端</td>
<td>123.456</td>
<td>79 E9 F6 42</td>
<td>0x42F6E979</td>
</tr>
</tbody></table>
<p>另外其对于取址对齐有很高的要求，32位数据要求地址也是 2 byte 对齐，例子如下。</p>
<pre><code class="language-c">uint16_t buffer[8];

uint32_t t32;

/* 以下两个实际上都会以 [0][1] 取内存转为 u32 */
// 在通常的CPU中，以下会以 [1][2] 取内存转 u32 
t32 = *(uint32_t *)(& buffer[1]); 
// 在通常的CPU中，以下是 [0][1] 正确零拷贝取法
t32 = *(uint32_t *)(& buffer[0]);

/* 以下两个则会以 [2][3] 取内存转为 u32 */
t32 = *(uint32_t *)(& buffer[3]);
t32 = *(uint32_t *)(& buffer[2]);</code></pre>
          <p style='text-align: right'>
          <a href='https://www.yono233.cn/notes/54#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">161317383669563440</guid>
  <category>note</category>
false
 </item>
  <item>
    <title>macOS 搭建 arm-gcc 构建环境</title>
    <link>https://www.yono233.cn/posts/novel/macos-arm-gcc</link>
    <pubDate>Mon, 20 Apr 2026 07:46:47 GMT</pubDate>
    <description>前置条件

mac 常用的包管理器是 brew(Homebrew)，首先需要安装这个环境，后续才好方</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/posts/novel/macos-arm-gcc'>https://www.yono233.cn/posts/novel/macos-arm-gcc</a></blockquote>
          <h1>前置条件</h1>
<p>mac 常用的包管理器是 brew(Homebrew)，首先需要安装这个环境，后续才好方便地安装 mac 中所谓的「命令行工具」。</p>
<p>而安装 brew 又需要<strong>可以访问 GitHub</strong>的前置条件，在 mac 系统中很难使用我最爱的 steam++，那么有两种最简单方法。一是使用最通用的 VPN 方式——clash；二则更简单更小白，使用 UU 加速器就可以，UU 加速器搜索「学术」，即可免费加速。</p>
<p>等确认可以正确访问 GitHub 之后，在终端中使用以下指令。<strong>注意，这个指令最后有提示如何操作 PASH</strong>。</p>
<pre><code class="language-sh">/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"</code></pre><p>安装完成后使用以下指令确认是否安装成功。</p>
<pre><code class="language-sh">brew -v</code></pre><h1>安装编译链和构建工具</h1>
<p>首先安装构建工具，<code>cmake</code> 和 <code>ninja</code>。</p>
<pre><code class="language-sh">brew install cmake ninja</code></pre><p>然后安装编译链，这里有一个问题，arm-gcc 不能直接使用 brew 安装原名包，这样可能是一种 without-headers 变体，缺少很多标准库和头文件。windows 下使用 msys2 管理也有类似这样的问题。</p>
<p>首先尝试卸载原名包和原名工具，如果有旧环境遗留，这一步将很重要。</p>
<pre><code class="language-sh">brew uninstall arm-none-eabi-gcc arm-none-eabi-binutils</code></pre><p>随后安装完整包</p>
<pre><code class="language-sh">brew install --cask gcc-arm-embedded

PKG="$(find "$(brew --caskroom)/gcc-arm-embedded" -name '*.pkg' | head -n 1)"
[ -n "$PKG" ] || { echo "未找到 gcc-arm-embedded 的 pkg"; exit 1; }
installer -pkg "$PKG" -target CurrentUserHomeDirectory

TOOLBIN="$(find "$HOME/Applications/ArmGNUToolchain" -type d -path '*/arm-none-eabi/bin' | sort | tail -n 1)"
[ -n "$TOOLBIN" ] || { echo "未找到 ArmGNUToolchain bin 目录"; exit 1; }
for t in gcc g++ as ar objcopy size gdb nm objdump ranlib readelf strip addr2line gcc-ar gcc-nm gcc-ranlib; do
	ln -sf "$TOOLBIN/arm-none-eabi-$t" "/opt/homebrew/bin/arm-none-eabi-$t"
done</code></pre><p>最后验证完整工具，需要输出包含正确的头文件路径以及类似 stdint 的头文件名。</p>
<pre><code class="language-sh">arm-none-eabi-gcc -v -E -x c - &lt; /dev/null</code></pre><p>然后就可以正常编译项目了</p>
<p></p>

          <p style='text-align: right'>
          <a href='https://www.yono233.cn/posts/novel/macos-arm-gcc#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">161317383661174828</guid>
  <category>post</category>
<category>禹步</category>
 </item>
  <item>
    <title>可能是你需要的第三台笔记本——macbook neo</title>
    <link>https://www.yono233.cn/posts/luxury/macbook-neo</link>
    <pubDate>Sat, 18 Apr 2026 21:15:27 GMT</pubDate>
    <description>总结

依然是总结写在前面，比起正派的高性能笔记本，neo 小姐当然不能胜任过多的工作。

但她确实</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/posts/luxury/macbook-neo'>https://www.yono233.cn/posts/luxury/macbook-neo</a></blockquote>
          <h1>总结</h1>
<p>依然是总结写在前面，比起正派的高性能笔记本，neo 小姐当然不能胜任过多的工作。</p>
<p>但她确实是我需要的<strong>第三台笔记本</strong>，第一台是工作用全能开发笔记本，第二台是出差专用可以随便删裁环境的笔记本。</p>
<p>她是伪装成笔记本的平板，自带支架和键盘，正适合我这样讨厌在触屏上写写画画，非常刚需键盘和触摸板的用户。没有风扇，不吵闹，超高续航，可以在床上毫无负担运行。</p>
<h1>到手</h1>
<p>目前日常叠国补能 3599 拿到，无论狗东、淘宝、并夕夕，大多数平台都能蹲到这个价格。叠教育补贴、以旧换新这些，我不太懂就没管。</p>
<p>这个粉色完全没有想象中精致，那我说真精致还得是 win 本有没有懂的。<a href="https://www.yono233.cn/posts/warning/24_8_5_LG">一台美丽的废物！LG gram style 16</a></p>
<p>而且相当重啊，单手拿捏对我是很大的负担。</p>
<p></p>
<h1>能做什么？</h1>
<h3>可以刷B站</h3>
<p>这就是我急迫拿下 <code>macbook刘奥</code> 的根本原因。我真的很需要一款能在床上侧躺着一路看视频到睡着的工具。作为自带支架的 ipad 模拟器，这台本子确实完美满足我的需求，和我期望中他的生态位没有太大的区别。</p>
<p>在这个价位下，没有风扇，没有通风孔，完整一体所以可以安心放在床上，甚至看起来还是--坚固型计算机--。除了 <code>macbook刘奥</code> 的确没有第二选择。</p>
<p></p>
<h3>可以写博客</h3>
<p>本文就是新的本子编撰。暂时使用 vscode 环境。</p>
<p></p>
<h3>用来写小说吗</h3>
<p>准备拿来写小说，所以安装了 node.js 环境。看起来如此轻量的开发需求，他是完全可以支持的。问题完全不在 neo 小姐，vscode 太吃内存真该死啊。</p>
<p>::: grid {cols=2,gap=4}</p>
<p></p>
<p></p>
<p>:::</p>
<h3>打游戏</h3>
<p>看着 app store 里的陈年游戏，我已经几乎 8 年没有接触 apple 的任何生态了吗。</p>
<p>可能是因为大约八年前苹果发明了世界上最愚蠢的额头摄像头，最愚蠢的灵动岛，这种设计以低成本的优势打垮了市面上所有的伸缩前置摄像头。他是摧毁我最爱的设计的始作俑者，我对果子一直有很大的偏见。</p>
<p>哪怕贫民窟的 neo 小姐使用的是手机级 cpu，但是她依然是高贵的「搭载 Apple 芯片的 Mac」，可以享受到苹果生态运行 ipad app。随便下了一个以前的老游戏，还是可以爽玩的。</p>
<p>我也不期望她的游戏体验，正常人还是会在主机上打游戏的。</p>
<p>::: grid {cols=2,gap=4}</p>
<p></p>
<p></p>
<p>:::</p>
<h3>剪视频</h3>
<p>找了一个样本，体验了一下用剪映剪视频，如果我的使用场景，仅仅是剪一下加点字幕，neo 小姐还是可以胜任的。就是 RAM 太小，导入和导出会有些慢。</p>
<h1>缺陷</h1>
<p>最大的缺陷当然是她真的非常<strong>重</strong>，我将其定义为超轻量的使用场景，但是这个重量真的不轻。</p>
<p>超弱的充电速度，神奇的 20W 充电。</p>
<p>触控板的问题，首先是所有评测都说过的，这是一个阉割特制的触控板，没有重力感应，需要机械按下。另外，我是不太常用<strong>按下</strong>这个操作，我更喜欢多指结合轻点，但是这块触控板的轻点似乎不太灵敏。</p>
<p>另外比较个人化的问题，她真的非常粘烟灰，仅仅是口吹或者干擦拭都弄不干净，必须湿纸巾来擦。</p>
<p>另外就没有其他缺陷了，如果你也需要一个<strong>没有触屏自带键盘和支架的 ipad</strong>，那么将非常合适。</p>

          <p style='text-align: right'>
          <a href='https://www.yono233.cn/posts/luxury/macbook-neo#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">161317383661174827</guid>
  <category>post</category>
<category>千机</category>
 </item>
  <item>
    <title>gitea 的 mail 配置</title>
    <link>https://www.yono233.cn/notes/53</link>
    <pubDate>Wed, 15 Apr 2026 09:17:15 GMT</pubDate>
    <description>部署 gitea 后一直没法自动发送通知邮件，作为未来的合作平台，今天打算解决这个问题。
官方文档或</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://www.yono233.cn/notes/53'>https://www.yono233.cn/notes/53</a></blockquote>
          <p>部署 gitea 后一直没法自动发送通知邮件，作为未来的合作平台，今天打算解决这个问题。
官方文档或者各种中文教程并没有解释清楚，我的应用环境下是在论坛老外的帖子中摸索出的正确配置。</p>
<p>配置段为</p>
<pre><code class="language-">[mailer]
ENABLED        = true
PROTOCOL       = smtps
SMTP_ADDR      = smtp.qq.com
SMTP_PORT      = 465
FROM           = yono233@foxmail.com
USER           = yono233@foxmail.com
PASSWD         = `你不应该知道这个`
ENABLE_HELO    = false</code></pre><p>关键点有三个</p>
<ul>
<li>使用 SMTP，PROTOCOL 不是 smtp 而应该是 smtps</li>
<li>对于 QQ 邮箱，SMTP_PORT 不能配置为 587，而应该使用 465</li>
<li>如果测试邮件报 EOF，需要添加 ENABLE_HELO 字段为 false</li>
<li></li>
</ul>

          <p style='text-align: right'>
          <a href='https://www.yono233.cn/notes/53#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">161317383669563439</guid>
  <category>note</category>
false
 </item>
  
</channel>
</rss>