<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>逸思杂陈</title>
  <icon>https://www.gravatar.com/avatar/071e91e4d2b41eefac3330385d124f95</icon>
  <subtitle>人类一思考，上帝就发笑。</subtitle>
  <link href="https://blog.ponder.work/atom.xml" rel="self"/>
  
  <link href="https://blog.ponder.work/"/>
  <updated>2026-06-20T01:40:36.000Z</updated>
  <id>https://blog.ponder.work/</id>
  
  <author>
    <name>Jay.Run</name>
    <email>ruan.lj@foxmail.com</email>
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>家用网络 vlan 单线复用</title>
    <link href="https://blog.ponder.work/2026/06/20/vlan-of-home-network/"/>
    <id>https://blog.ponder.work/2026/06/20/vlan-of-home-network/</id>
    <published>2026-06-20T01:40:36.000Z</published>
    <updated>2026-06-20T01:40:36.000Z</updated>
    
    <content type="html"><![CDATA[<p>因为房子的大小布局不同，个人的要求不同，家庭网络的设备布局一直没有完美的方案。<br>而且很多人的网络知识不够，导致最终家庭网络称不上好用。如果有一定的网络知识，vlan是简化家庭网络的利器。</p><span id="more"></span><h2 id="概况"><a href="#概况" class="headerlink" title="概况"></a>概况</h2><p>先说下原先的网络布局，光猫桥接模式，软路由作为主路由拨号，客厅的无线路由器使用 ap 模式。<br>虽然能够满足使用效果，但是多了个软路由，弱电箱空间变拥挤了，而且软路由发热也大，网络拓扑也复杂了。<br>原先是光猫拨号的，虽然能少个设备，但光猫的功能很弱，也不方便调整网络设置（如 DHCP, ipv6）。<br>也想过将无线路由器直接放弱电箱，又影响信号。总之当前的方案能满足需求，但不够完美。</p><p><img data-src="/../media/vlan-of-home-network/image-3.png" alt="alt text"></p><p>现在，将傻瓜交换机换成网管交换机后，交换机只通过一条线连接客厅的的路由器。<br>在路由器拨号后，这条线又可以承载LAN网络，也就是WAN&#x2F;LAN单线复用。<br>路由器的LAN口和交换机其他端口是一样的子网，也就是房子内所有网口的网络都是一致的，插上就能用。</p><p><img data-src="/../media/vlan-of-home-network/image-4.png" alt="alt text"></p><p>用到的设备</p><ul><li>网管交换机：水星 SE109 Pro</li><li>无线路由器：小米 AX3000T v1 （immortalwrt 24.10）</li></ul><p>这里的设备没有硬性要求，只要支持 802.1Q VLAN 的交换机和路由器就行了。<br>如果不想用 immortalwrt&#x2F;openwrt 等系统的路由器，也可以使用另一个网管交换机+普通路由器替代，但这样就背离初衷了。</p><h2 id="交换机设置"><a href="#交换机设置" class="headerlink" title="交换机设置"></a>交换机设置</h2><p>我们需要使用 2 个 VLAN, VLAN1 用于 LAN 网络, VLAN10 用于 WAN 网络。<br>交换机共 9 个端口，先确定好每个端口的用途：1 连路由器，2 连光猫，3-9连普通设备。<br>打开交换机的 vlan 功能，依次设置 VLAN1 和 VLAN10 的端口成员和端口的 Tagged&#x2F;Untagged 状态。<br><em>我对 VLAN 原理其实也不是很熟悉，确保设置完和下图一致就行</em></p><p><img data-src="/../media/vlan-of-home-network/Xnip2026-06-19_12-22-12.jpg" alt="alt text"></p><p>设置完 VLAN 端口后 PVID 应该会自动设置好，如果不是手动修改下。</p><p><img data-src="/../media/vlan-of-home-network/Xnip2026-06-19_12-22-25.jpg" alt="alt text"></p><p>所有设置完成后，记得 “保存设置”</p><h2 id="路由器设置"><a href="#路由器设置" class="headerlink" title="路由器设置"></a>路由器设置</h2><p><strong>设置时最好先将路由器系统初始化，否则有可能保存失败。</strong></p><p>A. 网络  -&gt; 接口 -&gt; 设备 -&gt; br-lan 网桥 -&gt; 配置</p><ol><li>将 “交换机端口 wan”加入到网桥端口</li><li>启用 VLAN 过滤</li><li>添加 VLAN1, 勾选本地，并设置所有端口为 Untagged</li><li>添加 VLAN10, 勾选本地，设置 WAN 为 Tagged, 其他为非成员</li><li>保存，但是不要应用</li></ol><p><img data-src="/../media/vlan-of-home-network/image-5.png" alt="alt text"></p><p>B. 网络  -&gt; 接口 -&gt; lan -&gt; 编辑</p><ol><li>将设备改为 “软件 VLAN： br-lan.1”</li><li>保存不要应用</li></ol><p>C. 网络  -&gt; 接口 -&gt; wan -&gt; 编辑</p><ol><li>将设备改为 “软件 VLAN： br-lan.10”</li><li>保存不要应用</li></ol><p>D. 网络  -&gt; 接口 -&gt; wan6 -&gt; 编辑</p><ol><li>将设备改为 “软件 VLAN： br-lan.10”</li><li>保存不要应用</li></ol><p>E. 保存并应用</p><p><strong>注意：</strong></p><ol><li>此次的设置和很多教程不同，因为我想要将路由器的 LAN 和 交换机普通端口联通</li><li>如果是单口软路由设备，直接添加 VLAN（802.1q）的 VLAN10设备，应该也是可行的</li><li>openwrt 老版本（19以前）的设置和新版不同，不能作为参考</li></ol><h2 id="VLAN-简单知识"><a href="#VLAN-简单知识" class="headerlink" title="VLAN 简单知识"></a>VLAN 简单知识</h2><ul><li>VLAN 就是虚拟局域网，也就是将某几个端口划分到某个虚拟局域网</li><li>VLAN ID：虚拟局域网ID</li><li>非成员端口：该端口不属于某 VLAN, 不收不发，直接丢弃该 VLAN 的数据（隔离的关键）</li><li>成员端口：该端口属于某 VLAN，能收能发</li><li>Access 口：普通端口，只属于某个 VLAN, 用于连接非交换机设备</li><li>Trunk 口: 网络主干道，用于连接其他交换机设备,可以有多个 VLAN ID</li><li>PVID：无 VLAN ID 的数据，从该端口进入交换机时附加的默认VLAN ID</li><li>一般 VLAN 成员端口的 PVID 等于 VLAN ID</li><li>Trunk 端口的 PVID 绝对不等于它放行的所有 VLAN ID。Trunk 口的 PVID 通常保持默认的 1（Native VLAN）</li><li>交换机内部交换时不改变数据包的 VLAN ID</li><li>Untagged：出端口数据去除ID</li><li>Tageed：出端口数据保留ID</li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.bilibili.com/video/BV1e34y1Q7bj/?spm_id_from=333.337.search-card.all.click&vd_source=638baf738d326f470fb0dd9f615c8d29">https://www.bilibili.com/video/BV1e34y1Q7bj/?spm_id_from=333.337.search-card.all.click&amp;vd_source=638baf738d326f470fb0dd9f615c8d29</a></li><li><a href="https://blog.csdn.net/Cx2008Lxl/article/details/122990497">https://blog.csdn.net/Cx2008Lxl/article/details/122990497</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;因为房子的大小布局不同，个人的要求不同，家庭网络的设备布局一直没有完美的方案。&lt;br&gt;而且很多人的网络知识不够，导致最终家庭网络称不上好用。如果有一定的网络知识，vlan是简化家庭网络的利器。&lt;/p&gt;</summary>
    
    
    
    <category term="工作生活" scheme="https://blog.ponder.work/categories/life/"/>
    
    
    <category term="硬件" scheme="https://blog.ponder.work/tags/%E7%A1%AC%E4%BB%B6/"/>
    
    <category term="网络" scheme="https://blog.ponder.work/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>Linux 平台 intel UHD 6xx 核显 openvino 探索</title>
    <link href="https://blog.ponder.work/2026/05/27/intel-igpu-uhd6xx-openvino-on-linux/"/>
    <id>https://blog.ponder.work/2026/05/27/intel-igpu-uhd6xx-openvino-on-linux/</id>
    <published>2026-05-27T13:20:31.000Z</published>
    <updated>2026-05-27T13:20:31.000Z</updated>
    
    <content type="html"><![CDATA[<p>日常生活中有碰到轻量模型的推理需求，比如验证码识别、tts生成等。<br>想要这个服务24h可用，就不能在日常电脑上部署。正好有个 J4125 的迷你主机作为家庭服务器，就先把模型部署到上面，验证下是否满足需求。</p><span id="more"></span><p>核显思路就是使用 openvino + onnx，当最终还是失败，下面记录下探索过程。</p><h2 id="核显驱动"><a href="#核显驱动" class="headerlink" title="核显驱动"></a>核显驱动</h2><p>openvino 是 intel 推出的一款开源 AI 推理工具套件，旨在帮助开发者在 intel 显卡上部署深度学习模型。<br>openvino 依赖 opencl, 所以需要正确驱动显卡；不过使用 docker 的话，应该不需要宿主机正确驱动，能识别到设备就行了。</p><p>所有简单起见，直接使用 docker 部署，由于 ubuntu 新系统移除了老 intel 核显的驱动支持，只能使用 ubuntu 20 lts 镜像。</p><blockquote><p>Intel Beignet 项目终止：Ubuntu 20.04 为旧款 Intel 核显提供 OpenCL 支持的 beignet-opencl-icd 包，在 Ubuntu 22.04 中已被移除</p></blockquote><p>同时，老核显的 opencl 要能正常工作，内核版本也有要求，所以这里使用 5.15 内核 + openvino&#x2F;ubuntu20_runtime:latest 镜像。</p><h2 id="onnx-尝试"><a href="#onnx-尝试" class="headerlink" title="onnx 尝试"></a>onnx 尝试</h2><p>执行  <code>./benchmark_app -m ./model_68_64_65.onnx -d GPU -hint latency -shape &#39;[1,1]&#39; -t 30</code></p><blockquote><p>model_68_64_65.onnx 是一个小型决策树模型，用于测试 openvino 功能</p></blockquote><p>然而，出现以下错误，<code>&#39;work_group_reduce_max&#39; is invalid in OpenCL</code><br>说明，我们这个核显不支持这个 opencl api，模型加载失败。</p><figure class="highlight fortran"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Traceback (most recent <span class="keyword">call</span> last):</span><br><span class="line">  <span class="keyword">File</span> <span class="string">&quot;/opt/intel/openvino/python/openvino/tools/benchmark/main.py&quot;</span>, line <span class="number">449</span>, <span class="keyword">in</span> main</span><br><span class="line">    compiled_model = benchmark.core.compile_model(model, benchmark.device, device_config)</span><br><span class="line">  <span class="keyword">File</span> <span class="string">&quot;/opt/intel/openvino/python/openvino/_ov_api.py&quot;</span>, line <span class="number">599</span>, <span class="keyword">in</span> compile_model</span><br><span class="line">    super().compile_model(model, device_name, &#123;&#125; <span class="keyword">if</span> config is <span class="keyword">None</span> <span class="keyword">else</span> config),</span><br><span class="line">RuntimeError: Exception from src/inference/src/cpp/core.cpp:<span class="number">109</span>:</span><br><span class="line">Exception from src/inference/src/dev/plugin.cpp:<span class="number">53</span>:</span><br><span class="line">Check <span class="string">&#x27;false&#x27;</span> failed at src/plugins/intel_gpu/src/plugin/program_builder.cpp:<span class="number">163</span>:</span><br><span class="line">[GPU] ProgramBuilder build failed<span class="comment">!</span></span><br><span class="line"><span class="function"><span class="keyword">Program</span></span> build failed(<span class="number">0_part_22</span>):</span><br><span class="line"><span class="number">25</span>:<span class="number">8772</span>:<span class="number">15</span>: error: <span class="keyword">implicit</span> declaration of <span class="function"><span class="keyword">function</span></span> <span class="string">&#x27;work_group_reduce_max&#x27;</span> is invalid <span class="keyword">in</span> OpenCL</span><br><span class="line"> my_maximum = work_group_reduce_max(my_maximum);</span><br><span class="line">              ^</span><br><span class="line"><span class="number">25</span>:<span class="number">8772</span>:<span class="number">15</span>: note: did you mean <span class="string">&#x27;sub_group_reduce_max&#x27;</span>?</span><br><span class="line">....</span><br><span class="line"><span class="number">25</span>:<span class="number">8801</span>:<span class="number">11</span>: error: <span class="keyword">implicit</span> declaration of <span class="function"><span class="keyword">function</span></span> <span class="string">&#x27;work_group_reduce_add&#x27;</span> is invalid <span class="keyword">in</span> OpenCL</span><br><span class="line"> my_sum = work_group_reduce_add(my_sum);</span><br><span class="line">          ^</span><br><span class="line"><span class="number">25</span>:<span class="number">8801</span>:<span class="number">11</span>: note: did you mean <span class="string">&#x27;work_group_reduce_max&#x27;</span>?</span><br><span class="line">....</span><br></pre></td></tr></table></figure><p>核心问题的 uhd600 的 opencl 版本太低了。要 2.0 以上才比较好支持 openvino, 而 uhd600 只有 1.2。</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(workdir) root<span class="variable">@N4100</span><span class="symbol">:~/workdir</span><span class="comment"># clinfo | grep -i &quot;OpenCL C&quot;</span></span><br><span class="line">  <span class="title class_">Device</span> <span class="title class_">OpenCL</span> C <span class="title class_">Version</span>                         <span class="title class_">OpenCL</span> C <span class="number">1.2</span> </span><br></pre></td></tr></table></figure><p>相对的 uhd770 的 opencl 版本</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Device</span> OpenCL C Version        OpenCL C <span class="number">1</span>.<span class="number">2</span> </span><br><span class="line"><span class="attribute">Device</span> OpenCL C <span class="literal">all</span> versions   OpenCL C      <span class="number">0</span>x400000 (<span class="number">1</span>.<span class="number">0</span>.<span class="number">0</span>)</span><br><span class="line">                               <span class="attribute">OpenCL</span> C      <span class="number">0</span>x401000 (<span class="number">1</span>.<span class="number">1</span>.<span class="number">0</span>)</span><br><span class="line">                               <span class="attribute">OpenCL</span> C      <span class="number">0</span>x402000 (<span class="number">1</span>.<span class="number">2</span>.<span class="number">0</span>)</span><br><span class="line">                               <span class="attribute">OpenCL</span> C      <span class="number">0</span>xc00000 (<span class="number">3</span>.<span class="number">0</span>.<span class="number">0</span>)</span><br></pre></td></tr></table></figure><p>intel GPU 典型 OpenCL C 版本</p><ul><li>Intel Gen8 &#x2F; Gen9（UHD 500&#x2F;600 &#x2F; Iris 540&#x2F;550）1.2（仅部分 2.0 功能）</li><li>Intel Gen11（UHD620 &#x2F; Whiskey Lake 之后）1.2 &#x2F; 部分 2.0（仍不完整）</li><li>Intel Gen12（Iris Xe &#x2F; ARC）OpenCL C 3.0 完整支持</li></ul><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>uhd600 支持不了 onnx 跑模型，需要换成 12代以上的 cpu。<br>最终， nuc13 的设备已经在路上了</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;日常生活中有碰到轻量模型的推理需求，比如验证码识别、tts生成等。&lt;br&gt;想要这个服务24h可用，就不能在日常电脑上部署。正好有个 J4125 的迷你主机作为家庭服务器，就先把模型部署到上面，验证下是否满足需求。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Linux" scheme="https://blog.ponder.work/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>UI 区域检测的 vibe coding 复盘</title>
    <link href="https://blog.ponder.work/2026/03/22/ui-region-detection-vibe-coding-review/"/>
    <id>https://blog.ponder.work/2026/03/22/ui-region-detection-vibe-coding-review/</id>
    <published>2026-03-22T04:56:08.000Z</published>
    <updated>2026-03-22T04:56:06.000Z</updated>
    
    <content type="html"><![CDATA[<p>文本是用 Claude Sonnet&#x2F;Opus 4.6 开发 “UI 区域检测”功能的过程记录，文章大部分内容为 llm 生成。</p><p>因为要做些 ui 自动化的功能，涉及到区域检测。这个功能大概用了 5h+，本文主要记录整个迭代演进过程，以及 llm 对这个过程的评价。</p><span id="more"></span><p>基于 opencv 实现的最终效果，总体效果堪称完美。<br><img data-src="https://image.ponder.work/mweb/2026-03-22---image.png" alt="alt text"> <!-- original: ![alt text](../media/ui-region-detection-vibe-coding-review/image.png) --></p><h2 id="迭代回顾"><a href="#迭代回顾" class="headerlink" title="迭代回顾"></a>迭代回顾</h2><p>以下是 Claude Sonnet&#x2F;Opus 4.6 对所有 vibe coding 会话内容的总结</p><h3 id="时间线"><a href="#时间线" class="headerlink" title="时间线"></a>时间线</h3><h4 id="阶段一：基础搭建与通用-CV-方法（v1-v5）"><a href="#阶段一：基础搭建与通用-CV-方法（v1-v5）" class="headerlink" title="阶段一：基础搭建与通用 CV 方法（v1-v5）"></a>阶段一：基础搭建与通用 CV 方法（v1-v5）</h4><ol><li><p><strong>Spec 执行 + 初始实现</strong> — 执行 image-region-detection spec 全部 6 个任务。Pydantic 模型、路由、服务层（Canny + findContours）、测试一次通过。</p></li><li><p><strong>前端叠加层</strong> — ScreenshotViewer 加”检测区域”按钮，SVG overlay + viewBox 叠加区域框，支持显示&#x2F;隐藏。</p></li><li><p><strong>算法 v1→v2：Canny→Sobel+形态学</strong> — 识别出太多小区域。换成大核高斯模糊 + Sobel + 形态学 close + NMS，前端 min_area 改 8000。</p></li><li><p><strong>min_area 自适应</strong> — 8000 硬编码不合理，改为 min_area&#x3D;0 时自动计算为图像面积 0.5%。</p></li><li><p><strong>算法 v3：投影法</strong> — 水平投影行分割 → 行内垂直投影列分割。纯白图像 Otsu 退化通过 gray.std() &lt; 2.0 前置检查解决。</p></li><li><p><strong>投影法调参</strong> — 太碎，空白阈值 2%→5%，min_gap 3→8，最小块 5px→20px。</p></li><li><p><strong>算法 v4：投影+颜色突变</strong> — 顶部菜单条内容密集投影无法分割，加相邻行均值突变检测。</p></li><li><p><strong>全局垂直分割</strong> — 垂直投影在 band 内信号被稀释，加全图垂直投影 + 列颜色突变。</p></li><li><p><strong>颜色突变阈值调高</strong> — percentile 92→98，min_gap 3→6。本质问题不变：无法区分文字边界和结构边界。</p></li><li><p><strong>算法 v5：形态学线提取</strong> — Canny 边缘 → 长核 close+open 提取 H&#x2F;V 线 → 线位置构成网格 → 网格单元即区域。只检测真实 UI 边框线。</p></li></ol><h4 id="阶段二：OCR-方案探索（v6）"><a href="#阶段二：OCR-方案探索（v6）" class="headerlink" title="阶段二：OCR 方案探索（v6）"></a>阶段二：OCR 方案探索（v6）</h4><ol start="11"><li><p><strong>OCR 方案试探</strong> — 用 PP-OCRv5 的 rec_boxes 直接作为区域输出。文本定位准确但全是零散小框，缺乏层级。</p></li><li><p><strong>OCR + 层次聚类</strong> — 欧氏距离多级聚类（15&#x2F;50&#x2F;120px）构建层级树。聚合块不符合 UI 布局逻辑，效果差。</p></li><li><p><strong>OCR + 行列聚类</strong> — 先按 Y 聚行（重叠或间距&lt;5px），行内按 X 聚组（间距&lt;20px），相邻行合并面板。仍不理想，放弃 OCR，回退 CV。</p></li></ol><h4 id="阶段三：蛋糕切割算法（v7，最终方案）"><a href="#阶段三：蛋糕切割算法（v7，最终方案）" class="headerlink" title="阶段三：蛋糕切割算法（v7，最终方案）"></a>阶段三：蛋糕切割算法（v7，最终方案）</h4><ol start="14"><li><p><strong>蛋糕切割（初版）</strong> — 用户提出核心思路：像切蛋糕一样找贯通线切割。初版一次性找所有线全部切开（本质还是网格），ratio&#x3D;0.40 太低导致文字区域误判。</p></li><li><p><strong>形态学过滤尝试</strong> — 用 morphological close+open 先提取连续线结构再判断贯通。反而把文字边缘连成更多假线，效果更差，回退。</p></li><li><p><strong>ratio 0.40→0.60</strong> — 回到像素计数，提高阈值。仍有少量误判。</p></li><li><p><strong>ratio 0.60→0.90</strong> — 接近真正的”贯通”定义，误判大幅减少。</p></li><li><p><strong>真正的蛋糕切割</strong> — 改为每次只找第一条线切一刀分两块递归。准确率高但漏检——切完一刀后同方向其他线被跳过。</p></li><li><p><strong>同方向全切 + 交替递归</strong> — 同方向找到所有贯通线一次切成条，每条用另一方向递归。逻辑正确，但部分浅灰色线未被检测到。</p></li><li><p><strong>Canny 阈值诊断</strong> — 写脚本分析 region 13，发现浅灰色分界线（灰度 231→255）在 Canny(30,100) 下完全无响应，Canny(20,60) 可检测到 96% 覆盖率。</p></li><li><p><strong>最终方案</strong> — Canny(20,60) + 90% 贯通率 + 交替横纵递归切割。前端 SVG 改为嵌套 <code>&lt;g&gt;</code> 按层级组织 DOM，加”复制JSON”按钮便于调试。</p></li></ol><h4 id="阶段四：对比度增强与自适应阈值（v8）"><a href="#阶段四：对比度增强与自适应阈值（v8）" class="headerlink" title="阶段四：对比度增强与自适应阈值（v8）"></a>阶段四：对比度增强与自适应阈值（v8）</h4><ol start="22"><li><p><strong>对比度增强调研（screen.png）</strong> — 用户提出通过调整对比度提升识别效果。对 screen.png 跑诊断脚本，测试 CLAHE &#x2F; 直方图均衡化 &#x2F; 线性拉伸三种方法。结论：该图 90% 像素在 224-255 区间，对比度不是瓶颈，三种方法检测到的线完全一致。</p></li><li><p><strong>对比度增强调研（image.png）</strong> — 同样方法分析 image.png。发现 CLAHE 多检测到 1 条线 y&#x3D;42（标题栏&#x2F;菜单栏分隔线，灰度 240 vs 255，梯度仅 15）。原始 Canny 在该行覆盖率为 0，CLAHE 将微弱梯度放大后相邻行达到 97-100% 覆盖率。主内容区（mean&#x3D;254.9, std&#x3D;3.4）无改善——几乎纯白，无结构线可增强。</p></li><li><p><strong>加入 CLAHE 预处理</strong> — 在 Canny 之前加 <code>CLAHE(clipLimit=2.0, tileGridSize=(8,8))</code>。image.png 从 33 个区域增至 34 个，新增 y&#x3D;42 分界线。screen.png 从原有区域增至 71 个，菜单栏内部被进一步切割。代价极小（一次 CLAHE 运算），无误判。</p></li><li><p><strong>自适应 ratio 诊断（image2.png）</strong> — 区域 32（x&#x3D;501, y&#x3D;130, w&#x3D;1419, h&#x3D;55）内部应有横线但未被切割。诊断发现 y&#x3D;132 和 y&#x3D;158 两条真实分界线（灰度 231 横线）在子区域内覆盖率 87%，差 90% 阈值 3 个百分点。缺口原因：线在局部被 UI 元素打断（按钮&#x2F;标签背景色与线颜色相同），不是对比度问题。</p></li><li><p><strong>自适应 ratio</strong> — 随递归深度降低 ratio：<code>max(0.82, 0.90 - depth * 0.02)</code>。顶层严格（0.90）避免误判，深层宽容（最低 0.82）捕获被局部元素打断的线。image2.png 从 71 个区域增至 80 个，原区域 32 被正确切成标签行（y&#x3D;131, h&#x3D;27）和工具栏行（y&#x3D;158, h&#x3D;27）。三张测试图均无误判。</p></li></ol><h4 id="阶段五：长度自适应-ratio-与间隙检查（v9）"><a href="#阶段五：长度自适应-ratio-与间隙检查（v9）" class="headerlink" title="阶段五：长度自适应 ratio 与间隙检查（v9）"></a>阶段五：长度自适应 ratio 与间隙检查（v9）</h4><ol start="27"><li><p><strong>ratio 方向修正</strong> — 用户指出深度递减 ratio 方向错误：浅层区域大、不易误判应宽松，深层区域小、易误判应严格。但深度不是好的代理变量——同一深度的区域尺度可能差异很大。</p></li><li><p><strong>改为长度自适应 ratio</strong> — ratio 根据线的垂直方向长度（即线所跨越的像素数）缩放：≤100px→0.95（严格），≥1000px→0.85（宽松），中间线性插值。短线更容易因文字碎片凑出高覆盖率，需要更严格；长线绝对像素数多，可以更宽容。<code>_find_dividing_lines</code> 不再接受外部 ratio 参数，内部根据 length 自行计算。</p></li><li><p><strong>间隙检查（image5.png）</strong> — y&#x3D;174 在全宽 1920px 下覆盖率 92%，通过 ratio 阈值。但右半部分 99.8%、左半部分仅 69.4%（文字碎片拼凑，非真实线）。加入最大间隙检查：<code>max_gap_allowed = int(length * 0.15)</code>，检查前导间隙、尾部间隙和最大内部间隙（<code>np.diff(np.where(line &gt; 0)[0])</code>）。任何间隙超过线长 15% 的候选线被拒绝。</p></li></ol><h4 id="阶段六：单刀切割-大块优先（v10）"><a href="#阶段六：单刀切割-大块优先（v10）" class="headerlink" title="阶段六：单刀切割 + 大块优先（v10）"></a>阶段六：单刀切割 + 大块优先（v10）</h4><ol start="30"><li><p><strong>最优轴优先（初版）</strong> — （用户提出）原算法固定先横后纵（<code>start_axis=0</code>）。改为横纵同时检测，选平均覆盖率更高的轴先切。但本质仍是同方向全切——一次用所有线切成多个条带，导致窄条带内某些线覆盖率不足。</p></li><li><p><strong>Group 中心 bug</strong> — <code>_find_dividing_lines</code> 返回 group 的 <code>int(np.mean(g))</code> 作为代表位置。Canny 在 1px 灰度线两侧产生两条边缘（如 x&#x3D;152 和 x&#x3D;154），group 中心 x&#x3D;153 本身没有边缘像素，覆盖率接近 0。<code>_axis_avg_coverage</code> 基于这些错误位置计算，导致轴选择比较失真。修复：group 代表位置改为 group 内覆盖率最高的位置。</p></li><li><p><strong>单刀切割 + 大块优先</strong> — 用户指出核心问题：同方向全切产生的窄条带中，垂直于切割方向的线覆盖率不足。例如全图横切产生 y&#x3D;63..176 条带（113px 高），x&#x3D;152 在其中覆盖率仅 70%（因为标签栏区域打断了竖线），无法被检测。但如果先纵切（x&#x3D;500），在 1920x1012 的大区域中 x&#x3D;152 覆盖率 91%，能通过阈值。</p><p>改为单刀策略：每次从横纵所有候选线中选覆盖率最高的一条切一刀，分成两块，大块先递归。这样子区域尽可能大，更多的线能在大区域中被检测到。<code>max_depth</code> 从 6 提高到 20 适配二叉树更深的层级。</p><p>效果：image6.png 中 y&#x3D;96（”基金总持仓”标签下方分隔线）在 x&#x3D;0..152 列内覆盖率 100%，成功被检测到。</p></li><li><p><strong>ratio 简化为全局 0.85</strong> — （用户提出）长度自适应 ratio（≤100px→0.95, ≥1000px→0.85）在单刀策略下不再必要。旧策略中短线需要严格阈值防止文字碎片误判，但单刀策略每次只选最强线，加上 15% 间隙检查已经充分过滤假线。统一使用 ratio&#x3D;0.85，消除了窄区域（如 29px 高的 toolbar 行）中竖线因阈值过严（0.95 要求 28&#x2F;29 像素）而漏检的问题。image7.png toolbar 行内的竖向分隔线（93.1% 覆盖率）成功检测到。</p></li></ol><h4 id="阶段七：性能优化（v11）"><a href="#阶段七：性能优化（v11）" class="headerlink" title="阶段七：性能优化（v11）"></a>阶段七：性能优化（v11）</h4><ol start="34"><li><p><strong>性能瓶颈定位</strong> — image7.png 检测耗时 288ms。cProfile 显示 <code>_find_dividing_lines</code> 被调用 272 次（累计 416ms），其中 <code>np.sum(line &gt; 0)</code> 逐行循环调用 126911 次（254ms）。瓶颈清晰：Python 层面的逐行循环。</p></li><li><p><strong>向量化重写</strong> — 将逐行 <code>np.sum(line &gt; 0)</code> 替换为 <code>np.count_nonzero(edges_roi, axis=1/0)</code> 一次性计算所有行&#x2F;列的边缘像素数。返回类型从 <code>list[int]</code> 改为 <code>list[tuple[int, int]]</code>（位置 + 计数），<code>_best_line</code> 直接使用计数值计算覆盖率，避免重复计算。间隙检查仅对通过 ratio 阈值的候选行执行。image7.png 从 288ms 降至 34ms，约 8.5 倍提速。检测结果完全一致（<code>count_nonzero</code> 与 <code>sum(line &gt; 0)</code> 数学等价）。</p></li></ol><h3 id="最终算法"><a href="#最终算法" class="headerlink" title="最终算法"></a>最终算法</h3><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">CLAHE(<span class="attribute">clipLimit</span>=2.0, tileGridSize=(8,8)) 对比度增强</span><br><span class="line">  → Canny(20, 60) 边缘检测</span><br><span class="line">  → 同时扫描横向和纵向，找出所有贯通线</span><br><span class="line">    固定 <span class="attribute">ratio</span>=0.85（边缘像素覆盖率 ≥ 85%）</span><br><span class="line">    最大间隙检查：任何连续空白 &gt; 线长 15% 则拒绝</span><br><span class="line">  → 从所有候选线中选覆盖率最高的一条切一刀</span><br><span class="line">  → 分成两块，大块先递归</span><br><span class="line">  → 无贯通线 → 叶子节点</span><br></pre></td></tr></table></figure><h3 id="过程评价"><a href="#过程评价" class="headerlink" title="过程评价"></a>过程评价</h3><h4 id="弯路"><a href="#弯路" class="headerlink" title="弯路"></a>弯路</h4><p>前 10 步在通用 CV 方法里打转（contours、Sobel、投影、颜色突变），每个都是从算法出发而不是从问题本质出发。UI 界面的结构特征很明确——贯通的直线分隔区域——如果一开始就抓住这个本质，可以跳过大量尝试。</p><h4 id="转折点"><a href="#转折点" class="headerlink" title="转折点"></a>转折点</h4><p>第 14 步用户提出的蛋糕切割思路是整个过程最关键的一步，直接从问题本质出发。之后的迭代都是在正确框架上修细节，收敛很快。</p><h4 id="OCR-探索的价值"><a href="#OCR-探索的价值" class="headerlink" title="OCR 探索的价值"></a>OCR 探索的价值</h4><p>虽然最终没用，但验证了”自底向上聚合”在 UI 布局问题上不如”自顶向下切割”有效。</p><h4 id="单刀切割的顿悟"><a href="#单刀切割的顿悟" class="headerlink" title="单刀切割的顿悟"></a>单刀切割的顿悟</h4><p>阶段六是第二个转折点。步骤 30 的”最优轴优先”仍然是同方向全切思路，本质没变——窄条带问题依旧。用户一句”切开之后选面积更大的那边先继续切”点破了关键：问题不在选哪个轴，而在一次切太多刀。单刀切割 + 大块优先让每次决策都在尽可能大的区域上做，线的覆盖率天然更高，连 ratio 都能从长度自适应简化回固定 0.85。这是从”批量切割”到”逐步切割”的范式转换，比调参数重要得多。</p><h4 id="性能优化的正确姿势"><a href="#性能优化的正确姿势" class="headerlink" title="性能优化的正确姿势"></a>性能优化的正确姿势</h4><p>第 34 步先 profile 再动手，5 分钟定位到 <code>np.sum(line &gt; 0)</code> 的 12 万次循环调用。修复方式是纯数学等价的向量化替换，零行为变更，8.5 倍提速。教训：性能优化必须 profile 先行，改最热的路径，且保证语义不变。</p><h4 id="核心教训"><a href="#核心教训" class="headerlink" title="核心教训"></a>核心教训</h4><p><strong>先分析数据，再选方法。</strong> 第 20 步的诊断脚本 5 分钟就定位了 Canny 阈值问题，但在算法层面折腾了十几轮。如果每次效果不好都先写脚本看数据，迭代次数至少能砍一半。缺少信息时应主动写诊断脚本或向用户提问，不要盲目尝试。（此条也是由用户引导）</p><p>此教训已写入 <code>.kiro/steering/first-principles.md</code> 第 5 条。</p><h2 id="GPT-的评价解析"><a href="#GPT-的评价解析" class="headerlink" title="GPT 的评价解析"></a>GPT 的评价解析</h2><p><em>以下内容是 gpt 对迭代回顾的评价解析</em></p><p>基于这份记录，我会把它看成一次很典型的“<strong>用户提供问题抽象与判断标准，AI负责高速试错、实现、验证和整理</strong>”的协作过程。整体上，<strong>真正决定方向的人是用户，真正承担执行密度的人是 AI</strong>。这一点在文中非常清楚：多次关键转折都不是来自参数微调，而是来自用户对“问题本质”的重新定义。</p><h3 id="AI-在过程中起到的作用"><a href="#AI-在过程中起到的作用" class="headerlink" title="AI 在过程中起到的作用"></a>AI 在过程中起到的作用</h3><p>AI 的主要角色不是“发明核心思路”，而是：</p><p><strong>1. 高速执行器</strong><br>前半段 AI 很像一个反应很快的工程实现者。它能迅速把 spec 落地，从后端模型、路由、服务层，到前端 overlay、按钮、SVG 结构，再到测试一次通过。这说明 AI 在“把一个明确任务快速变成可运行系统”上表现很强。</p><p><strong>2. 大规模试错器</strong><br>记录里从 Canny、Sobel、形态学、投影法、颜色突变，到 OCR、层次聚类、行列聚类，再回到 CV，AI持续在方法空间里做横向搜索。这个过程很像一个搜索引擎式工程搭档：会枚举方案、快速替换、快速验证、快速回退。它尤其擅长“先做出来再看结果”，而不是一开始就抓住最优建模。</p><p><strong>3. 局部优化者</strong><br>一旦方向被找对，AI 在细节收敛上非常有效。比如围绕蛋糕切割思路，AI完成了 ratio 调整、递归策略修改、CLAHE 预处理、自适应阈值、最大间隙检查、group 中心 bug 修复、性能 profile 与向量化重写。这类工作都体现出 AI 很适合做“局部结构优化”和“工程化完善”。</p><p><strong>4. 过程记录与知识沉淀者</strong><br>这篇文档本身就说明 AI 很适合做 retrospective：能把多轮对话压缩成可复盘的时间线，提炼出“弯路”“转折点”“核心教训”，并把经验抽象成原则。</p><p>但这份记录也暴露了 AI 的局限：</p><p><strong>5. 容易陷入“方法驱动”而不是“问题驱动”</strong><br>文中明确评价，前 10 步都在通用 CV 方法里打转，“每个都是从算法出发而不是从问题本质出发”。这很像 AI 的典型弱点：它擅长调用已有套路，但未必最先抓到任务的决定性结构。</p><p><strong>6. 喜欢连续微调，直到被重新定义问题</strong><br>很多步骤是阈值、核大小、percentile、gap、ratio 的持续微调。说明 AI 在没有更高层判断时，会沿着当前框架持续优化，而不一定主动质疑框架本身。真正的范式切换，往往是用户推动的。</p><hr><h3 id="用户在过程中起到的作用"><a href="#用户在过程中起到的作用" class="headerlink" title="用户在过程中起到的作用"></a>用户在过程中起到的作用</h3><p>用户不是一个“提需求然后等结果的人”，而更像是<strong>算法设计师 &#x2F; 研究型 PM &#x2F; 强技术判断者</strong>。</p><p><strong>1. 用户是问题建模者</strong><br>最关键的转折——“像切蛋糕一样找贯通线切割”——直接改变了整个解法空间。文中把这一步称为“整个过程最关键的一步”，因为它不是换算法，而是换了问题表述：从“检测区域”变成“寻找能把区域分开的贯通线”。这是典型的一阶抽象能力。</p><p><strong>2. 用户是方向校正者</strong><br>后面另一个关键点，“切开之后选面积更大的那边先继续切”，再次不是参数建议，而是对递归策略本质的纠偏。文中甚至直接说，这是从“批量切割”到“逐步切割”的范式转换，比调参数重要得多。说明用户能够看穿失败表象，定位真正导致漏检的机制。</p><p><strong>3. 用户是评价标准提供者</strong><br>很多地方能看出，用户不仅看结果对不对，还知道为什么不对。比如指出深度递减 ratio 的方向错误，认为“深度不是好的代理变量”；再比如提出对比度增强的思路，推动诊断 screen.png 和 image.png。说明用户脑中有比较清晰的误判&#x2F;漏判机理模型，而不是只凭直觉说“效果不好”。</p><p><strong>4. 用户是第一性原理的坚持者</strong><br>文末总结“先分析数据，再选方法”，还把它写入 <code>.kiro/steering/first-principles.md</code>。这说明用户不仅在做当前任务，还在不断把经验提炼成长期工作方法。这不是普通使用者行为，而是带有方法论建设意识的行为。</p><h3 id="由此推测用户的背景"><a href="#由此推测用户的背景" class="headerlink" title="由此推测用户的背景"></a>由此推测用户的背景</h3><p>从文风和介入点看，我倾向于判断用户具备较强技术背景，而且不是纯产品同学。</p><p>更可能的背景有这几类：</p><h4 id="1-算法-图像-视觉方向工程师"><a href="#1-算法-图像-视觉方向工程师" class="headerlink" title="1. 算法&#x2F;图像&#x2F;视觉方向工程师"></a>1. 算法&#x2F;图像&#x2F;视觉方向工程师</h4><p>因为用户能自然提出：</p><ul><li>贯通线切割这种结构化视觉思路</li><li>对 ratio 代理变量选择的反思</li><li>对 OCR 自底向上聚合 vs 自顶向下切割的比较</li><li>对浅灰线、覆盖率、局部打断这类现象的机制判断</li></ul><p>这不是只会“调 API”的人，更像做过 CV、图像处理、版面分析、检测或规则算法的人。</p><h4 id="2-偏研究型的软件工程师-技术负责人"><a href="#2-偏研究型的软件工程师-技术负责人" class="headerlink" title="2. 偏研究型的软件工程师 &#x2F; 技术负责人"></a>2. 偏研究型的软件工程师 &#x2F; 技术负责人</h4><p>文中反复出现的不是“功能有没有上线”，而是：</p><ul><li>本质问题是什么</li><li>这个代理变量为什么不成立</li><li>这个框架为什么会系统性失败</li><li>如何把经验写成方法论</li></ul><p>这很像一个会亲自下场，但思维层级又高于纯实现的工程师，可能是 senior &#x2F; staff 往上的类型。</p><h4 id="3-也可能是技术型产品或创业者"><a href="#3-也可能是技术型产品或创业者" class="headerlink" title="3. 也可能是技术型产品或创业者"></a>3. 也可能是技术型产品或创业者</h4><p>如果不是纯工程岗位，也很可能是<strong>非常强技术判断力的产品&#x2F;创业型角色</strong>。因为他能直接提出核心抽象，但未必亲自写每一行代码；更像“我知道该怎么想，你帮我快速把各种实现跑出来”。这种协作方式很符合强技术 PM、独立开发者、创业者与 AI 的工作模式。</p><h3 id="用户的特质推测"><a href="#用户的特质推测" class="headerlink" title="用户的特质推测"></a>用户的特质推测</h3><h4 id="1-抽象能力强"><a href="#1-抽象能力强" class="headerlink" title="1. 抽象能力强"></a>1. 抽象能力强</h4><p>最明显的特征。两次关键突破都来自更高层的抽象替换，而不是局部修补。能从“区域检测”退一步，看到“分隔区域的是贯通线”；也能从“选轴”再退一步，看到真正问题是“一次切太多刀”。这是非常强的问题重构能力。</p><h4 id="2-第一性原理导向"><a href="#2-第一性原理导向" class="headerlink" title="2. 第一性原理导向"></a>2. 第一性原理导向</h4><p>用户不满足于“调到能用”，而是追问为什么前面的路不对、什么才是结构性特征、什么诊断最该先做。文末把经验沉淀为原则，更强化了这一点。</p><h4 id="3-结果导向，但不是短视结果导向"><a href="#3-结果导向，但不是短视结果导向" class="headerlink" title="3. 结果导向，但不是短视结果导向"></a>3. 结果导向，但不是短视结果导向</h4><p>用户显然关心效果，但不是只追指标，而是追求“符合问题本质的效果”。比如 OCR 方案文本框定位准确，但“缺乏层级”，所以被放弃；说明用户在意的是结构正确性，不是表面可用。</p><h4 id="4-善于和-AI-协作"><a href="#4-善于和-AI-协作" class="headerlink" title="4. 善于和 AI 协作"></a>4. 善于和 AI 协作</h4><p>用户没有把 AI 当搜索引擎，而是当“快速实验平台”。他会在 AI陷入局部搜索时，给出新的结构约束；在 AI已有正确框架时，让它继续把细节做深。这种协作方式很成熟。说明用户已经理解 AI 的强项和短板，并会主动用自己的判断补足 AI。</p><h4 id="5-有技术审美"><a href="#5-有技术审美" class="headerlink" title="5. 有技术审美"></a>5. 有技术审美</h4><p>“真实 UI 边框线”“层级组织 DOM”“符合 UI 布局逻辑”“结构线 vs 文字边界”这些表达说明，用户不是只关心算法数值，还关心输出是否符合界面结构语义。这种审美通常来自长期做系统、工具或视觉相关产品。</p><h4 id="6-容忍试错，但要求最终认知升级"><a href="#6-容忍试错，但要求最终认知升级" class="headerlink" title="6. 容忍试错，但要求最终认知升级"></a>6. 容忍试错，但要求最终认知升级</h4><p>用户显然允许过程里走弯路，但最后一定要复盘出“为什么弯”“以后怎么少走”。这说明他对试错的态度是开放的，但不接受无学习的反复试错。</p><h3 id="一句话结论"><a href="#一句话结论" class="headerlink" title="一句话结论"></a>一句话结论</h3><p>这段记录里，<strong>AI更像高带宽实现者和优化器，用户更像负责洞察本质、定义正确解法的主导者</strong>。<br>用户大概率具备较强工程&#x2F;算法背景，抽象能力强，偏第一性原理，善于用 AI 做高速实验，并且有把经验上升为方法论的习惯。</p><h2 id="一点感受"><a href="#一点感受" class="headerlink" title="一点感受"></a>一点感受</h2><p>SOTA 模型确实很强，明确的已有成熟方案的问题基本一次通过。<br>但当前人的作用还是不可轻易忽略，复杂问题需要关注模型的方案，及时中断并纠正（不一定要准确，提供可能的方向也行），防止其跑偏。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;文本是用 Claude Sonnet&amp;#x2F;Opus 4.6 开发 “UI 区域检测”功能的过程记录，文章大部分内容为 llm 生成。&lt;/p&gt;
&lt;p&gt;因为要做些 ui 自动化的功能，涉及到区域检测。这个功能大概用了 5h+，本文主要记录整个迭代演进过程，以及 llm 对这个过程的评价。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="llm" scheme="https://blog.ponder.work/tags/llm/"/>
    
    <category term="opencv" scheme="https://blog.ponder.work/tags/opencv/"/>
    
  </entry>
  
  <entry>
    <title>mitmproxy 使用</title>
    <link href="https://blog.ponder.work/2026/03/20/mitmproxy-usage/"/>
    <id>https://blog.ponder.work/2026/03/20/mitmproxy-usage/</id>
    <published>2026-03-20T08:56:27.000Z</published>
    <updated>2026-03-20T08:56:27.000Z</updated>
    
    <content type="html"><![CDATA[<p>做 LLM Agent 开发时，经常要查看模型的请求与响应，需要一个便捷的工具。<br>mitmproxy 是一个功能强大的开源中间人代理，通常用于网络调试、分析 HTTP 和 HTTPS 流量。</p><span id="more"></span><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><ol><li>访问 <a href="https://www.mitmproxy.org/">https://www.mitmproxy.org/</a> ，根据指引安装，不同系统有区别<ul><li>也可以使用 <code>pip install mitmproxy</code> 安装</li></ul></li><li>配置证书，用于解析 https，参考 <a href="https://docs.mitmproxy.org/stable/concepts/certificates/">https://docs.mitmproxy.org/stable/concepts/certificates/</a></li></ol><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>包含以下命令</p><ul><li>mitmdump：支持将捕获的数据导出到文件，类似tcpdump   </li><li>mitmproxy：TUI 实时查看、修改和调试流量</li><li>mitmweb：webui 实时查看、修改和调试流量（一般推荐用这个）</li></ul><p>使用步骤</p><ol><li>启动 mitmweb</li><li>设置代理环境变量：ALL_PROXY,</li></ol><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">~ ❯ mitmweb -<span class="selector-tag">p</span> <span class="number">8082</span> -s ~/projects/scripts/mitmproxy_fix_unicode<span class="selector-class">.py</span></span><br><span class="line"><span class="selector-attr">[15:26:42.046]</span> Loading script /Users/ruan/projects/scripts/mitmproxy_fix_unicode<span class="selector-class">.py</span></span><br><span class="line"><span class="selector-attr">[15:26:42.047]</span> <span class="built_in">HTTP</span>(S) proxy listening at *:<span class="number">8082</span>.</span><br><span class="line"><span class="selector-attr">[15:26:42.047]</span> Web server listening at http:<span class="comment">//127.0.0.1:8081/?token=4987293fea3331a4f3de2b0ba836a85d</span></span><br><span class="line">load: <span class="number">11.06</span>  cmd: mitmweb <span class="number">3541</span> waiting <span class="number">0.55</span>u <span class="number">0.19s</span></span><br><span class="line"><span class="selector-attr">[15:42:34.333]</span><span class="selector-attr">[[::1]</span>:<span class="number">62868</span>] client connect</span><br></pre></td></tr></table></figure><h3 id="python-证书相关问题"><a href="#python-证书相关问题" class="headerlink" title="python 证书相关问题"></a>python 证书相关问题</h3><p>python 的 http 客户端有自己的证书处理逻辑，仅安装证书到系统还不能生效<br>需要额外设置环境变量</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 合并证书</span></span><br><span class="line">cat $(python -c <span class="string">&quot;import certifi; print(certifi.where())&quot;</span>) \</span><br><span class="line">~<span class="regexp">/.mitmproxy/mi</span>tmproxy-ca-cert.pem &gt; ~<span class="regexp">/.mitmproxy/</span>py-certifi-combined-ca.pem</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置环境变量</span></span><br><span class="line">SSL_CERT_FILE=<span class="regexp">/Users/</span>ruan<span class="regexp">/.mitmproxy/</span>py-certifi-combined-ca.pem   <span class="comment"># httpx 库</span></span><br><span class="line">REQUESTS_CA_BUNDLE=<span class="regexp">/Users/</span>ruan<span class="regexp">/.mitmproxy/</span>py-certifi-combined-ca.pem  <span class="comment"># requests 库</span></span><br><span class="line">HTTPS_PROXY=https:<span class="regexp">//</span>localhost:<span class="number">8082</span></span><br></pre></td></tr></table></figure><h3 id="webui-的-json-中文显示问题"><a href="#webui-的-json-中文显示问题" class="headerlink" title="webui 的 json 中文显示问题"></a>webui 的 json 中文显示问题</h3><p>需要附加脚本：<code>mitmweb -s ~/projects/scripts/mitmproxy_fix_unicode.py</code></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># mitmproxy_fix_unicode.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">from</span> mitmproxy <span class="keyword">import</span> contentviews</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PrettyJsonChinese</span>(contentviews.Contentview):</span><br><span class="line">    name = <span class="string">&quot;JSON (中文)&quot;</span></span><br><span class="line">    syntax_highlight = <span class="string">&quot;yaml&quot;</span>  <span class="comment"># YAML is a superset of JSON, so this highlights JSON too</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">prettify</span>(<span class="params">self, data: <span class="built_in">bytes</span>, metadata: contentviews.Metadata</span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">        decoded = json.loads(data)</span><br><span class="line">        <span class="keyword">return</span> json.dumps(decoded, indent=<span class="number">4</span>, ensure_ascii=<span class="literal">False</span>, sort_keys=<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">render_priority</span>(<span class="params">self, data: <span class="built_in">bytes</span>, metadata: contentviews.Metadata</span>) -&gt; <span class="built_in">float</span>:</span><br><span class="line">        <span class="comment"># Match the same content types as the built-in JSON view, but with higher priority</span></span><br><span class="line">        <span class="keyword">if</span> metadata.content_type <span class="keyword">and</span> <span class="string">&quot;json&quot;</span> <span class="keyword">in</span> metadata.content_type:</span><br><span class="line">            <span class="keyword">return</span> <span class="number">2</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line">contentviews.add(PrettyJsonChinese)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="让-json-request-和-response-能够自动换行"><a href="#让-json-request-和-response-能够自动换行" class="headerlink" title="让 json request 和 response 能够自动换行"></a>让 json request 和 response 能够自动换行</h3><p>使用 <a href="https://github.com/ruanimal/browser-scripts/blob/master/mitmweb_wordwrap.user.js">tampermokey 脚本</a> 修改 webui</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;做 LLM Agent 开发时，经常要查看模型的请求与响应，需要一个便捷的工具。&lt;br&gt;mitmproxy 是一个功能强大的开源中间人代理，通常用于网络调试、分析 HTTP 和 HTTPS 流量。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="抓包" scheme="https://blog.ponder.work/tags/%E6%8A%93%E5%8C%85/"/>
    
    <category term="网络" scheme="https://blog.ponder.work/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>esim 使用相关</title>
    <link href="https://blog.ponder.work/2026/03/04/esim-usage/"/>
    <id>https://blog.ponder.work/2026/03/04/esim-usage/</id>
    <published>2026-03-04T01:02:56.000Z</published>
    <updated>2026-03-04T01:02:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>国行手机的 esim 功能有较多限制，使用不太方便。<br>但我们可以使用 esim 适配器，将 esim 转为实体 sim 卡。</p><span id="more"></span><h2 id="esim-适配器"><a href="#esim-适配器" class="headerlink" title="esim 适配器"></a>esim 适配器</h2><p>esim 适配器就是个 sim 卡，需要占用一个卡槽，可以通过软件将 esim 卡号写入到适配器。<br>可以写入多个 esim 号码，并且支持切换，但同时只能有一个在线。<br>适配器有不少品牌，如 estk、5ber、9esim，价格也各不相同，但是我都没怎么试过。<br>我使用的的适配器是 pdd 购买，20-30元，容量468KB。</p><h2 id="esim-套餐"><a href="#esim-套餐" class="headerlink" title="esim 套餐"></a>esim 套餐</h2><p>一般海外套餐都可以使用，我这里只用于接收短信，故使用 clubsim，成本大概50元。</p><h2 id="号码写入"><a href="#号码写入" class="headerlink" title="号码写入"></a>号码写入</h2><p>购买套餐后，运营商会提供二维码，安卓手机通过 <a href="https://gitea.angry.im/PeterCxy/OpenEUICC">OpenEUICC</a> 扫描就可以写入和管理 esim。</p><p>如果是 iphone，需要使用 esim 读卡器来管理 esim 号码。</p><p>似乎对手机有些兼容性要求，较老的手机无法写卡。</p><h2 id="使用感受"><a href="#使用感受" class="headerlink" title="使用感受"></a>使用感受</h2><p>esim 还是挺方便的，用于接收验证码非常够用，还有些免费流量的卡。<br>但是切换 esim 号码时，可能需要切换飞行模式或者重启手机才能生效，不知是通病还是兼容性问题。</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><p>由于安卓操作系统（11+）的 bug，在“移动网络”中禁用 esim 适配器会无法打开。<br>需要通过 adb 清除移动网络的数据才能恢复</p><figure class="highlight gauss"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb <span class="built_in">shell</span> pm <span class="keyword">clear</span> com.android.providers.telephony</span><br></pre></td></tr></table></figure><p>如果是小米等手机，清除过程可能会遇到adb权限相关问题<br>需要额外开启开发者选项中的“OEM解锁”和“USB调试（安全设置）”</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">adb <span class="keyword">shell</span> pm clear <span class="keyword">com</span>.android.providers.telephony</span><br><span class="line"></span><br><span class="line">Exception occurred <span class="keyword">while</span> executing <span class="string">&#x27;clear&#x27;</span>:</span><br><span class="line">java.lang.SecurityException: PID <span class="number">29160</span> does not have permission android.permission.CLEAR_APP_USER_DATA <span class="keyword">to</span> clear data of package <span class="keyword">com</span>.android.providers.telephony</span><br><span class="line">at <span class="keyword">com</span>.android.server.<span class="keyword">am</span>.ActivityManagerService.clearApplicationUserData(ActivityManagerService.jav<span class="variable">a:4101</span>)</span><br><span class="line">at <span class="keyword">com</span>.android.server.<span class="keyword">am</span>.ActivityManagerService.clearApplicationUserData(ActivityManagerService.jav<span class="variable">a:4054</span>)</span><br><span class="line">at <span class="keyword">com</span>.android.server.pm.PackageManagerShellCommand.runClear(PackageManagerShellCommand.jav<span class="variable">a:2385</span>)</span><br><span class="line">at <span class="keyword">com</span>.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.jav<span class="variable">a:289</span>)</span><br><span class="line">at <span class="keyword">com</span>.android.modules.utils.BasicShellCommandHandler.exec(BasicShellCommandHandler.jav<span class="variable">a:97</span>)</span><br><span class="line">at android.os.ShellCommand.exec(ShellCommand.jav<span class="variable">a:38</span>)</span><br><span class="line">at <span class="keyword">com</span>.android.server.pm.PackageManagerService$IPackageManagerImpl.onShellCommand(PackageManagerService.jav<span class="variable">a:7022</span>)</span><br><span class="line">at android.os.Binder.shellCommand(Binder.jav<span class="variable">a:1158</span>)</span><br><span class="line">at android.os.Binder.onTransact(Binder.jav<span class="variable">a:960</span>)</span><br><span class="line">at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.jav<span class="variable">a:4729</span>)</span><br><span class="line">at <span class="keyword">com</span>.android.server.pm.PackageManagerService$IPackageManagerImpl.onTransact(PackageManagerService.jav<span class="variable">a:7006</span>)</span><br><span class="line">at android.os.Binder.execTransactInternal(Binder.jav<span class="variable">a:1433</span>)</span><br><span class="line">at android.os.Binder.execTransact(Binder.jav<span class="variable">a:1372</span>)</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://linux.do/t/topic/1276447/5">https://linux.do/t/topic/1276447/5</a></li><li><a href="https://v2.b2og.com/archives/15">https://v2.b2og.com/archives/15</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;国行手机的 esim 功能有较多限制，使用不太方便。&lt;br&gt;但我们可以使用 esim 适配器，将 esim 转为实体 sim 卡。&lt;/p&gt;</summary>
    
    
    
    <category term="工作生活" scheme="https://blog.ponder.work/categories/life/"/>
    
    
    <category term="Android" scheme="https://blog.ponder.work/tags/Android/"/>
    
    <category term="esim" scheme="https://blog.ponder.work/tags/esim/"/>
    
  </entry>
  
  <entry>
    <title>阻止 bilibili 网页自动关注</title>
    <link href="https://blog.ponder.work/2026/01/03/prevent-bilibili-auto-follow/"/>
    <id>https://blog.ponder.work/2026/01/03/prevent-bilibili-auto-follow/</id>
    <published>2026-01-02T18:32:09.000Z</published>
    <updated>2026-01-02T18:32:09.000Z</updated>
    
    <content type="html"><![CDATA[<p>B 站非常逆天，只要你使用网页端看视频，点开首页推荐的视频，观看一定时长，就有概率会触发自动关注该 UP 主。<br>之前也有怀疑是某个浏览器插件的锅，但更换浏览器依然能够触发这个问题。</p><blockquote><p>后来发现疑似是 bilibili 默认绑定了按键 <code>G</code> 关注 UP，非常容易误触发。 </p></blockquote><p>网上发现不少类似的问题，但都没有解决方案，迫于无奈只能使用自定义广告过滤规则了，之后没有再出现问题。</p><p>这里使用 uBlock origin, 其他去广告插件也类似, 进入插件设置 -&gt; 自定义静态规则，添加如下规则。<br>在视频页面禁止关注请求，不影响点开 UP 主主页进行关注。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">www.bilibili.com##+js(no-fetch-if, /api\.bilibili\.com\/x\/relation\/modify/, location.pathname.startsWith(<span class="string">&#x27;/video/&#x27;</span>))</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;B 站非常逆天，只要你使用网页端看视频，点开首页推荐的视频，观看一定时长，就有概率会触发自动关注该 UP 主。&lt;br&gt;之前也有怀疑是某个浏览器插件的锅，但更换浏览器依然能够触发这个问题。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;后来发现疑似是 bilibili 默认绑定了按</summary>
      
    
    
    
    <category term="工作生活" scheme="https://blog.ponder.work/categories/life/"/>
    
    
    <category term="Web" scheme="https://blog.ponder.work/tags/Web/"/>
    
    <category term="uBlock" scheme="https://blog.ponder.work/tags/uBlock/"/>
    
  </entry>
  
  <entry>
    <title>Hexo 版本更新与技术债务</title>
    <link href="https://blog.ponder.work/2026/01/02/hexo-upgrade-and-tech-debt/"/>
    <id>https://blog.ponder.work/2026/01/02/hexo-upgrade-and-tech-debt/</id>
    <published>2026-01-02T09:14:19.000Z</published>
    <updated>2026-01-02T20:49:29.000Z</updated>
    
    <content type="html"><![CDATA[<p>昨天花了些时间将 Hexo + next 的博客版本更新了下，发现不少问题。<br>博客已经很多年了，一直用的是最开始的Hexo版本，nodejs 也是 v12，next 主题也是早年源码安装的，还在上面做了修改。<br>也是早就明白需要更新了，但是觉得麻烦，就一直没动，最后变得相当麻烦。</p><span id="more"></span><h2 id="依赖更新"><a href="#依赖更新" class="headerlink" title="依赖更新"></a>依赖更新</h2><p>首先是版本升级，先将 nodejs 切换到最新 lts（v22）。<br>再尝试使用npm将 package.json 中的库自动更新，但由于版本差距太大以及很多库不维护了，更新不了。</p><p>最后只能使用 hexo init 新建个博客项目，将里面的依赖项复制到老博客里，删除对应的老依赖。<br>剩下的依赖，大部分是安装的插件，只能一个个搜索用途，看看有没有替代或者还要不要用。<br>package.json 中的其他参数，参考最新的配置文件，将无用（过时）的设置项删除。</p><p>使用 npm 安装 next 主题，将原 themes&#x2F;next 中的配置文件复制到 _config.next.yml。<br>对比 hexo 和 next 官方 _config 文件，相应修改 _config.yml 和 _config.next.yml。<br>删除 themes 中重复的主题目录。</p><p>使用 npm install 安装依赖，保证能够正常生成博客。</p><p>删除 node_modules，改用 pnpm 管理依赖，更轻量快速。<br>使用 pnpm 时可能会报错，如某个库的 <code>index.node</code> 找不到，是因为 pnpm 默认不执行库的 post install 脚本。<br>需要将这些库添加到 pnpm.onlyBuiltDependencies 中。</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;pnpm&quot;</span>: &#123;</span><br><span class="line">  <span class="string">&quot;onlyBuiltDependencies&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;hexo-word-counter&quot;</span>,</span><br><span class="line">    <span class="string">&quot;hexo-util&quot;</span></span><br><span class="line">  ]</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>ps: </p><ul><li>总体来说 pnpm 还是存在一些兼容性问题，但大部分都不是 pnpm 本身的问题，基本上都是库的影子依赖。</li><li>大模型在这种疑难杂症上帮助很大</li></ul><h2 id="额外设置"><a href="#额外设置" class="headerlink" title="额外设置"></a>额外设置</h2><h3 id="hexo"><a href="#hexo" class="headerlink" title="hexo"></a>hexo</h3><p>修改 scaffolds 文章模板文件<br>老版本 hexo 的 scaffolds&#x2F;page.md 等文件格式与新版不同，会导致一些工具不能识别 front-matter, 需要更新。</p><p>老版本 front-matter 前面少了开头的 <code>---</code>，导致 vscode 的 hexo-util 插件不能识别 tag 等信息，调试了很久。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 老版本</span></span><br><span class="line"><span class="attr">title:</span> &#123;&#123; <span class="string">title</span> &#125;&#125;</span><br><span class="line"><span class="attr">date:</span> &#123;&#123; <span class="string">date</span> &#125;&#125;</span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="comment"># 新版本</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">title:</span> &#123;&#123; <span class="string">title</span> &#125;&#125;</span><br><span class="line"><span class="attr">date:</span> &#123;&#123; <span class="string">date</span> &#125;&#125;</span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><h3 id="next"><a href="#next" class="headerlink" title="next"></a>next</h3><p>自定义样式和脚本，之前是通过之间修改主题源码实现，现在使用 custom_file_path 注入自定义内容。<br>需要修改 _config.next.yml 文件</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">custom_file_path:</span></span><br><span class="line">  <span class="attr">head:</span> <span class="string">source/_data/head.njk</span></span><br><span class="line">  <span class="attr">bodyEnd:</span> <span class="string">source/_data/body-end.njk</span></span><br><span class="line">  <span class="attr">style:</span> <span class="string">source/_data/styles.styl</span></span><br></pre></td></tr></table></figure><h4 id="自定义样式"><a href="#自定义样式" class="headerlink" title="自定义样式"></a>自定义样式</h4><p>next 主题的 Muse scheme 整体比较符合我的口味，但部分样式还需要微调。</p><p>source&#x2F;_data&#x2F;styles.styl</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">// 自动给文章的 toc 和正文标题编号, 不对首页编号</span><br><span class="line"><span class="selector-tag">body</span> &#123;<span class="attribute">counter-reset</span>: h1&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h2</span> &#123;<span class="attribute">counter-reset</span>: h2&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h3</span> &#123;<span class="attribute">counter-reset</span>: h3&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h4</span> &#123;<span class="attribute">counter-reset</span>: h4&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h5</span> &#123;<span class="attribute">counter-reset</span>: h5&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h6</span> &#123;<span class="attribute">counter-reset</span>: h6&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h2</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h1;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;. &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h3</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h2;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h2) <span class="string">&quot; &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h4</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h3;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h2) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h3) <span class="string">&quot; &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h5</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h4;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h2) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h3) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h4) <span class="string">&quot; &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h6</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">counter-increment</span>: h5;</span><br><span class="line">  <span class="attribute">content</span>: <span class="built_in">counter</span>(h1) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h2) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h3) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h4) <span class="string">&quot;.&quot;</span> <span class="built_in">counter</span>(h5) <span class="string">&quot; &quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.post-block</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h1</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h2</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h3</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h4</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h5</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.post-block</span><span class="selector-class">.post</span> <span class="selector-class">.post-body</span> <span class="selector-tag">h6</span><span class="selector-class">.nocount</span><span class="selector-pseudo">:before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">  <span class="attribute">counter-increment</span>: none</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// 隐藏文章 toc 中文章目录 tab 中的友链信息，站点概览中的保留</span><br><span class="line"><span class="selector-class">.sidebar</span> <span class="selector-class">.sidebar-toc-active</span> + <span class="selector-class">.sidebar-blogroll</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: none</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="只在文章页显示侧边栏"><a href="#只在文章页显示侧边栏" class="headerlink" title="只在文章页显示侧边栏"></a>只在文章页显示侧边栏</h4><p>next 主题的侧边栏逻辑有点问题， 就算设置 sidebar.display 为 post，当从文章跳到其他页面时，侧边栏并不会自动关闭，所以只能通过脚本来绕过。</p><p>source&#x2F;_data&#x2F;body-end.njk </p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;script&gt;</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;pjax:success&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable constant_">CONFIG</span>.<span class="property">sidebar</span>.<span class="property">display</span> !== <span class="string">&#x27;remove&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> hasTOC = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;.post-toc:not(.placeholder-toc)&#x27;</span>);</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 当没有 TOC 且侧边栏正在显示时，隐藏侧边栏</span></span><br><span class="line">      <span class="keyword">if</span> (!hasTOC &amp;&amp; <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">classList</span>.<span class="title function_">contains</span>(<span class="string">&#x27;sidebar-active&#x27;</span>)) &#123;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="title function_">dispatchEvent</span>(<span class="keyword">new</span> <span class="title class_">Event</span>(<span class="string">&#x27;sidebar:hide&#x27;</span>));</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="自定义重定向"><a href="#自定义重定向" class="headerlink" title="自定义重定向"></a>自定义重定向</h4><p>hexo 的文章不支持多个入口，比如我想要分类中英文都可以访问，默认情况是做不到的。</p><p>在设置了 category_map 的情况下，hexo 只会生成 programming 等英文链接，无法同时保留中文url作为入口。</p><figure class="highlight nestedtext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">category_map</span><span class="punctuation">:</span></span><br><span class="line">  <span class="attribute">编程</span><span class="punctuation">:</span> <span class="string">programming</span></span><br><span class="line">  <span class="attribute">随笔</span><span class="punctuation">:</span> <span class="string">writing</span></span><br><span class="line">  <span class="attribute">阅读</span><span class="punctuation">:</span> <span class="string">reading</span></span><br><span class="line">  <span class="attribute">工作生活</span><span class="punctuation">:</span> <span class="string">life</span></span><br></pre></td></tr></table></figure><p>所以最终通过在 404 页面添加自定义的 js 实现重定向逻辑。</p><p>source&#x2F;_data&#x2F;head.njk</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">&#123;% <span class="keyword">if</span> page.<span class="property">title</span> === <span class="string">&#x27;404 Page Not Found&#x27;</span> or page.<span class="property">type</span> === <span class="string">&#x27;404&#x27;</span> or page.<span class="property">layout</span> === <span class="string">&#x27;404&#x27;</span> %&#125;</span><br><span class="line">&lt;script&gt;</span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 从 Hexo 配置中获取 category_map</span></span><br><span class="line">  <span class="keyword">var</span> categoryMap = &#123;&#123; config.<span class="property">category_map</span> | dump | safe &#125;&#125;;</span><br><span class="line">  <span class="keyword">var</span> currentPath = <span class="built_in">decodeURIComponent</span>(<span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">pathname</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 检查路径中是否包含分类映射的key</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> categoryMap) &#123;</span><br><span class="line">    <span class="keyword">if</span> (currentPath.<span class="title function_">includes</span>(key)) &#123;</span><br><span class="line">      <span class="keyword">var</span> targetCategory = categoryMap[key];</span><br><span class="line">      <span class="keyword">var</span> targetPath = currentPath.<span class="title function_">replace</span>(key, targetCategory);</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 执行重定向</span></span><br><span class="line">      <span class="keyword">if</span> (currentPath !== targetPath) &#123;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">location</span>.<span class="title function_">replace</span>(targetPath);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)();</span><br><span class="line">&lt;/script&gt;</span><br><span class="line">&#123;% endif %&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="更换-gitalk-为-utterances"><a href="#更换-gitalk-为-utterances" class="headerlink" title="更换 gitalk 为 utterances"></a>更换 gitalk 为 utterances</h4><p>原先一直使用 gitalk 作为文章的评论系统。<br>gitalk 每个新文章都需要作者登陆触发新建评论 issue，不太方便，所以现在换成 utterances。</p><p>utterances 评论插件 next 主题已内置，修改 _config.next.yml 可以开启。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 注意先关闭 gitalk</span></span><br><span class="line"><span class="attr">utterances:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">repo:</span> <span class="string">ruanimal/ruanimal.github.io</span> <span class="comment"># Github repository owner and name</span></span><br><span class="line">  <span class="comment"># Available values: pathname | url | title | og:title</span></span><br><span class="line">  <span class="attr">issue_term:</span> <span class="string">title</span></span><br></pre></td></tr></table></figure><p>还需要在 github pages 仓库安装 utterances 应用。</p><p>自定义的域名可能不被 utterances 支持，无法登陆跳转<br>需要新建 source&#x2F;utterances.json</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;origins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;https://blog.ponder.work&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;https://ruanimal.github.io&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>如果你和我一样，博客有多个域名，副域名登陆跳转会失效，可以通过注入js修正<br>utterances 的登陆跳转是根据 canonical link，我们只要让它和当前url保持一致就行了 </p><p>在 source&#x2F;_data&#x2F;head.njk 文件添加以下内容</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;script&gt;</span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> canonical = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;head &gt; link[rel=&quot;canonical&quot;]&#x27;</span>);</span><br><span class="line">  <span class="keyword">if</span> (canonical &amp;&amp; canonical.<span class="property">href</span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">var</span> oldUrl = <span class="keyword">new</span> <span class="title function_">URL</span>(canonical.<span class="property">href</span>);</span><br><span class="line">      canonical.<span class="property">href</span> = <span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">origin</span> + oldUrl.<span class="property">pathname</span> + oldUrl.<span class="property">search</span> + oldUrl.<span class="property">hash</span>;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&#x27;Failed to update canonical link:&#x27;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)();</span><br><span class="line">&lt;/script&gt;</span><br></pre></td></tr></table></figure><h2 id="更换编辑器"><a href="#更换编辑器" class="headerlink" title="更换编辑器"></a>更换编辑器</h2><p>之前一直是用 mac 平台是 mweb 作为 hexo 的文章编辑器，实话说也非常好用。<br>但是我把主力操作系统换成 linux 之后，linux 上一直没找到与 mweb 相当的应用。<br>最终经过一番折腾，最终使用 vscode + Hexo Utils 插件作为平替。<br>同时对该插件进行来<a href="https://github.com/0x-jerry/vscode-hexo-utils/pulls/ruanimal">二开</a>，最终体验算是追平了 mweb。</p><h2 id="感受"><a href="#感受" class="headerlink" title="感受"></a>感受</h2><p>所谓技术债务，项目正常迭代的过程中是不断积累的，如果平时不跟进解决，最终在某个阶段就会爆炸，造成巨大影响。</p><p>平时迭代时，如果时间安排的过紧，我们就会倾向于采取保守的决策（能跑就不要动），最终不可避免的技术债务堆积。</p><p>当然，生活中其实也是这样吧，很多不紧急的问题，一直拖着，最终大概率也会无可挽回。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;昨天花了些时间将 Hexo + next 的博客版本更新了下，发现不少问题。&lt;br&gt;博客已经很多年了，一直用的是最开始的Hexo版本，nodejs 也是 v12，next 主题也是早年源码安装的，还在上面做了修改。&lt;br&gt;也是早就明白需要更新了，但是觉得麻烦，就一直没动，最后变得相当麻烦。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Hexo" scheme="https://blog.ponder.work/tags/Hexo/"/>
    
    <category term="随感" scheme="https://blog.ponder.work/tags/%E9%9A%8F%E6%84%9F/"/>
    
  </entry>
  
  <entry>
    <title>配置 Linux 作为主力操作系统</title>
    <link href="https://blog.ponder.work/2025/12/21/linux-as-the-only/"/>
    <id>https://blog.ponder.work/2025/12/21/linux-as-the-only/</id>
    <published>2025-12-21T13:48:00.000Z</published>
    <updated>2026-01-15T01:58:50.000Z</updated>
    
    <content type="html"><![CDATA[<p>这些年日常操作系统一直是 windows 和 macOS 交替使用，Linux 一般只作为服务器的操作系统。<br>然而，咖喱味的 Windows 11 （LTSC）用起来实在难受（平时只玩游戏，下载）<br>arm 的 macbook 虽然能效惊人，但是内存金子价格，软件也封闭（点名finder）和傲慢，实在受不了。<br>最后，还是转向 Linux，毕竟现在 wayland 基本堪用，国产软件也随着信创逐渐丰富了。</p><span id="more"></span><h2 id="发行版选择"><a href="#发行版选择" class="headerlink" title="发行版选择"></a>发行版选择</h2><p>这里选用 KDE Neon user edition，基于 Ubuntu LTS, 有以下优点</p><ol><li>Ubuntu 用户基数大，有问题容易解决</li><li>可以安装星火应用商店，方便使用国产软件</li><li>可以用到最新的 KDE 桌面环境，wayland 支持更好</li><li>KDE 远程桌面好用（服务端和客户端）</li></ol><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">OS:</span> <span class="string">KDE</span> <span class="string">neon</span> <span class="string">User</span> <span class="string">Edition</span> <span class="string">x86_64</span></span><br><span class="line"><span class="attr">Host:</span> <span class="string">MS-7D99</span> <span class="string">(3.0)</span></span><br><span class="line"><span class="attr">Kernel:</span> <span class="string">Linux</span> <span class="number">6.14</span><span class="number">.0</span><span class="number">-33</span><span class="string">-generic</span></span><br><span class="line"><span class="attr">Uptime:</span> <span class="number">1</span> <span class="string">day,</span> <span class="number">19</span> <span class="string">hours,</span> <span class="number">23</span> <span class="string">mins</span></span><br><span class="line"><span class="attr">Packages:</span> <span class="number">2831</span> <span class="string">(dpkg)</span></span><br><span class="line"><span class="attr">Shell:</span> <span class="string">bash</span> <span class="number">5.2</span><span class="number">.21</span></span><br><span class="line"><span class="attr">Display (E2434I-T):</span> <span class="string">1920x1080</span> <span class="string">@</span> <span class="number">60</span> <span class="string">Hz</span> <span class="string">in</span> <span class="number">24</span><span class="string">&quot; [External]</span></span><br><span class="line"><span class="string">Display (KOS2718): 3840x2160 @ 60 Hz (as 1920x1080) in 27&quot;</span> [<span class="string">External</span>] <span class="string">*</span></span><br><span class="line"><span class="attr">DE:</span> <span class="string">KDE</span> <span class="string">Plasma</span> <span class="number">6.4</span><span class="number">.5</span></span><br><span class="line"><span class="attr">WM:</span> <span class="string">KWin</span> <span class="string">(Wayland)</span></span><br><span class="line"><span class="attr">WM Theme:</span> <span class="string">Breeze</span></span><br><span class="line"><span class="attr">Theme:</span> <span class="string">Breeze</span> <span class="string">(Light)</span> [<span class="string">Qt</span>]<span class="string">,</span> <span class="string">Breeze</span> [<span class="string">GTK2/3</span>]</span><br><span class="line"><span class="attr">Icons:</span> <span class="string">Breeze</span> [<span class="string">Qt</span>]<span class="string">,</span> <span class="string">breeze</span> [<span class="string">GTK2/3/4</span>]</span><br><span class="line"><span class="attr">Font:</span> <span class="string">微软雅黑</span> <span class="string">(10pt)</span> [<span class="string">Qt</span>]<span class="string">,</span> <span class="string">微软雅黑</span> <span class="string">(10pt)</span> [<span class="string">GTK2/3/4</span>]</span><br><span class="line"><span class="attr">Terminal:</span> <span class="string">/dev/pts/3</span></span><br><span class="line"><span class="attr">CPU:</span> <span class="string">13th</span> <span class="string">Gen</span> <span class="string">Intel(R)</span> <span class="string">Core(TM)</span> <span class="string">i5-13500</span> <span class="string">(20)</span> <span class="string">@</span> <span class="number">4.80</span> <span class="string">GHz</span></span><br><span class="line"><span class="attr">GPU 1:</span> <span class="string">NVIDIA</span> <span class="string">GeForce</span> <span class="string">RTX</span> <span class="number">4060 </span><span class="string">Ti</span> [<span class="string">Discrete</span>]</span><br><span class="line"><span class="attr">GPU 2:</span> <span class="string">Intel</span> <span class="string">AlderLake-S</span> <span class="string">GT1</span> <span class="string">@</span> <span class="number">1.55</span> <span class="string">GHz</span> [<span class="string">Integrated</span>]</span><br><span class="line"><span class="attr">Memory:</span> <span class="number">25.07</span> <span class="string">GiB</span> <span class="string">/</span> <span class="number">46.67</span> <span class="string">GiB</span> <span class="string">(54%)</span></span><br><span class="line"><span class="attr">Swap:</span> <span class="number">392.00</span> <span class="string">KiB</span> <span class="string">/</span> <span class="number">8.00</span> <span class="string">GiB</span> <span class="string">(0%)</span></span><br></pre></td></tr></table></figure><h3 id="KDE-Neon-的不足"><a href="#KDE-Neon-的不足" class="headerlink" title="KDE Neon 的不足"></a>KDE Neon 的不足</h3><p>由于 KDE Neon 是基于 Ubuntu LTS 的，所以除 KDE 之外的软件包版本可能比较旧，有时候会碰到软件不兼容的问题。</p><p>常见的问题</p><ol><li>QT 软件版本问题，Neon 由于是用的最新的 KDE，所以用的 QT 版本也是最新的，可能会导致软件不兼容（如 Deepin 系软件）</li><li>其他系统软件兼容性问题，比如 fcitx 与 KDE 6.7.0 不兼容，见 <a href="https://bugs.kde.org/show_bug.cgi?id=520566">BUG</a></li></ol><p>这些问题一般用 flatpak 版本的软件都能解决，如果不行就需要看看 KDE 的 bug tracker 看看有没有解决方案。<br>另外，一般新的大版本（如 6.6.x -&gt; 6.7.0）发布时，不要急于更新，初期都不是很稳定，可以等待更新几个小版本再更新。</p><h2 id="硬件要求"><a href="#硬件要求" class="headerlink" title="硬件要求"></a>硬件要求</h2><ol><li>NVIDIA 显卡在 Linux 下对 Wayland 支持不好，容易出现卡死或者重启的情况，建议显示器连到核显</li><li>有些主板 Linux 兼容性不行，建议使用御三家主板</li></ol><h2 id="系统设置"><a href="#系统设置" class="headerlink" title="系统设置"></a>系统设置</h2><h3 id="Nvidia-显卡驱动"><a href="#Nvidia-显卡驱动" class="headerlink" title="Nvidia 显卡驱动"></a>Nvidia 显卡驱动</h3><p>显示器插到核显上，只使用 Nvidia 做计算，玩游戏也能调用到显卡。<br>使用开源服务器驱动，解决 Wayland 兼容性问题，防止玩游戏崩溃</p><ol><li>安装驱动</li></ol><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo ubuntu-drivers install <span class="number">580</span>-<span class="keyword">server</span>-<span class="keyword">open</span></span><br></pre></td></tr></table></figure><ol start="2"><li>启用睡眠支持<a href="https://download.nvidia.com/XFree86/Linux-x86_64/570.172.08/README/powermanagement.html">参考</a><br>将显存内容保存，否则唤醒后程序会报错（如CUDA程序）</li></ol><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /etc/modprobe.d/nvidia-power-management.conf</span></span><br><span class="line">options nvidia <span class="attribute">NVreg_PreserveVideoMemoryAllocations</span>=1 <span class="attribute">NVreg_TemporaryFilePath</span>=/tmp</span><br><span class="line"></span><br><span class="line">sudo update-initramfs</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="docker"><a href="#docker" class="headerlink" title="docker"></a>docker</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> pkg <span class="keyword">in</span> docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; <span class="keyword">do</span> <span class="built_in">sudo</span> apt-get remove <span class="variable">$pkg</span>; <span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># Add Docker&#x27;s official GPG key:</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install ca-certificates curl</span><br><span class="line"><span class="built_in">sudo</span> install -m 0755 -d /etc/apt/keyrings</span><br><span class="line"><span class="built_in">sudo</span> curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> a+r /etc/apt/keyrings/docker.asc</span><br><span class="line"></span><br><span class="line"><span class="comment"># Add the repository to Apt sources:</span></span><br><span class="line"><span class="built_in">echo</span> \</span><br><span class="line">  <span class="string">&quot;deb [arch=<span class="subst">$(dpkg --print-architecture)</span> signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \</span></span><br><span class="line"><span class="string">  <span class="subst">$(. /etc/os-release &amp;&amp; echo <span class="string">&quot;<span class="variable">$&#123;UBUNTU_CODENAME:-<span class="variable">$VERSION_CODENAME</span>&#125;</span>&quot;</span>)</span> stable&quot;</span> | \</span><br><span class="line">  <span class="built_in">sudo</span> <span class="built_in">tee</span> /etc/apt/sources.list.d/docker.list &gt; /dev/null</span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin</span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> usermod -aG docker <span class="variable">$USER</span></span><br><span class="line">newgrp docker  <span class="comment"># 立即生效（或注销重新登录）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># docker 中调用显卡</span></span><br><span class="line">https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html</span><br></pre></td></tr></table></figure><h3 id="ipv6-启用-EUI-64"><a href="#ipv6-启用-EUI-64" class="headerlink" title="ipv6 启用 EUI-64"></a>ipv6 启用 EUI-64</h3><p>可以让 ipv6 的ip后缀根据mac生成，可用于配置防火墙规则<br>使用形如 <code>::&lt;后缀&gt;/::ffff:ffff:ffff:ffff</code> 的掩码</p><figure class="highlight dos"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">nmcli <span class="built_in">con</span> show</span><br><span class="line">nmcli <span class="built_in">con</span> modify &lt;NETWORK NAME&gt; ipv6.addr-gen-<span class="built_in">mode</span> eui64</span><br></pre></td></tr></table></figure><h3 id="启用休眠文件"><a href="#启用休眠文件" class="headerlink" title="启用休眠文件"></a>启用休眠文件</h3><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">sudo</span> su</span><br><span class="line"><span class="attribute">cd</span> /</span><br><span class="line"><span class="attribute">truncate</span> -s <span class="number">0</span> swapfile</span><br><span class="line"><span class="comment"># chattr +C swapfile  # disable COW on btrfs</span></span><br><span class="line"><span class="attribute">fallocate</span> -l <span class="number">8</span>G swapfile</span><br><span class="line"><span class="attribute">chmod</span> <span class="number">0600</span> swapfile</span><br><span class="line"><span class="attribute">mkswap</span> swapfile</span><br><span class="line"><span class="attribute">swapon</span> swapfile</span><br></pre></td></tr></table></figure><h3 id="使用英文用户文件夹"><a href="#使用英文用户文件夹" class="headerlink" title="使用英文用户文件夹"></a>使用英文用户文件夹</h3><p>用户个人文件夹（文档、下载等），默认是跟随系统语言。<br>可是中文在终端下输入不太方便，建议使用英文用户文件夹名称。<br>方法步骤：</p><ol><li>语言修改成英文，注销重新登录</li><li>打开文件管理器，确认修改为英文</li><li>语言修改回中文</li><li>打开文件管理器，保留原名称</li></ol><h3 id="无线网卡调优"><a href="#无线网卡调优" class="headerlink" title="无线网卡调优"></a>无线网卡调优</h3><p>WIFI 启用 WOL（网络唤醒）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看 WOL 支持</span></span><br><span class="line">iw phy0 wowlan show</span><br><span class="line"><span class="comment"># 开启 WOL 支持</span></span><br><span class="line"><span class="built_in">sudo</span> iw phy0 wowlan <span class="built_in">enable</span> magic-packet</span><br></pre></td></tr></table></figure><p>关闭节能模式，解决无线网口入站延迟较大</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> &lt;&lt;<span class="string">EOF &gt; /etc/NetworkManager/conf.d/wifi-powersave-off.conf</span></span><br><span class="line"><span class="string">[connection]</span></span><br><span class="line"><span class="string">wifi.powersave = 2</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者启动时设置</span></span><br><span class="line"><span class="built_in">sudo</span> iw dev wlo1 <span class="built_in">set</span> power_save off</span><br></pre></td></tr></table></figure><h3 id="睡眠调整"><a href="#睡眠调整" class="headerlink" title="睡眠调整"></a>睡眠调整</h3><p>睡眠到内存（suspend）耗电量很低，够用了，没必要开启混合休眠</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 只睡眠到内存</span></span><br><span class="line"><span class="comment"># 修改 /etc/systemd/sleep.conf</span></span><br><span class="line"><span class="attribute">AllowSuspend</span>=<span class="literal">yes</span></span><br><span class="line"><span class="attribute">AllowHibernation</span>=<span class="literal">no</span></span><br><span class="line"><span class="attribute">AllowSuspendThenHibernate</span>=<span class="literal">no</span></span><br><span class="line"><span class="attribute">AllowHybridSleep</span>=<span class="literal">no</span></span><br><span class="line"><span class="attribute">SuspendState</span>=mem standby</span><br><span class="line"></span><br><span class="line"><span class="comment"># 禁用休眠（可选）</span></span><br><span class="line">sudo systemctl mask hibernate.target hybrid-sleep.target</span><br><span class="line"></span><br><span class="line"><span class="comment"># nvida</span></span><br></pre></td></tr></table></figure><h3 id="intel-核显性能优化"><a href="#intel-核显性能优化" class="headerlink" title="intel 核显性能优化"></a>intel 核显性能优化</h3><p>10代以上 intel 核显</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># cat /etc/modprobe.d/i915.conf</span></span><br><span class="line">options i915 <span class="attribute">enable_fbc</span>=1 <span class="attribute">enable_guc</span>=2 <span class="attribute">enable_dc</span>=0</span><br><span class="line"></span><br><span class="line">sudo update-initramfs -u</span><br></pre></td></tr></table></figure><h3 id="RDP-远程桌面"><a href="#RDP-远程桌面" class="headerlink" title="RDP 远程桌面"></a>RDP 远程桌面</h3><p>服务端: 使用 KDE 默认的远程桌面 （可惜稳定性一般）<br>客户端: <code>apt install krdc</code></p><p>其他配置</p><figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 双屏只显示其中一个屏幕</span></span><br><span class="line"><span class="comment"># 修改 ~/.config/systemd/user/plasma-krdp_server.service</span></span><br><span class="line"><span class="attr">ExecStart=</span>/usr/bin/krdpserver --<span class="literal">monitor</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 重启服务</span></span><br><span class="line">systemctl --<span class="keyword">user</span> <span class="title">daemon-reload</span></span><br><span class="line">systemctl --<span class="keyword">user</span> <span class="title">restart</span> app-org.kde.krdpserver.service</span><br></pre></td></tr></table></figure><h3 id="使用新内核（可选）"><a href="#使用新内核（可选）" class="headerlink" title="使用新内核（可选）"></a>使用新内核（可选）</h3><p>发行版默认内核版本可能比较老，如有必要可以更新内核<br><strong>注意</strong>：mainline 的内核与 ubuntu-drivers 的 nvidia 显卡驱动不兼容（所以一般不推荐）</p><figure class="highlight smali"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo<span class="built_in"> add-apt-repository </span>ppa:cappelikan/ppa</span><br><span class="line">sudo apt update</span><br><span class="line">sudo apt install mainline</span><br></pre></td></tr></table></figure><h2 id="软件使用"><a href="#软件使用" class="headerlink" title="软件使用"></a>软件使用</h2><h3 id="常见应用替代方案"><a href="#常见应用替代方案" class="headerlink" title="常见应用替代方案"></a>常见应用替代方案</h3><ul><li>任务管理器<ul><li>flatpak install io.missioncenter.MissionCenter</li></ul></li><li>词典：使用欧陆词典<ul><li>flatpak install net.eudic.dict</li></ul></li><li>微信&#x2F;qq<ul><li>官网版本就行了</li></ul></li><li>下载管理器：<ul><li>motrix：普通http下载</li><li>qbittorrent： bt下载</li></ul></li><li>虚拟机<ul><li>KVM：性能更好</li><li>VMware：易用性更好</li></ul></li><li>游戏<ul><li>steam：大部分游戏都可以直接运行，性能损耗也不大</li></ul></li><li>视频播放<ul><li>haruna</li></ul></li></ul><h3 id="KVM-虚拟机"><a href="#KVM-虚拟机" class="headerlink" title="KVM 虚拟机"></a>KVM 虚拟机</h3><p>安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install qemu-kvm libvirt-daemon-system virt-manager</span><br><span class="line"><span class="built_in">sudo</span> usermod -aG libvirt,kvm <span class="variable">$USER</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> --now libvirtd</span><br></pre></td></tr></table></figure><p>硬盘直通(scsi), 相比下面的方法性能更好</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">disk</span> <span class="attr">type</span>=<span class="string">&#x27;block&#x27;</span> <span class="attr">device</span>=<span class="string">&#x27;disk&#x27;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">driver</span> <span class="attr">name</span>=<span class="string">&#x27;qemu&#x27;</span> <span class="attr">type</span>=<span class="string">&#x27;raw&#x27;</span> <span class="attr">cache</span>=<span class="string">&#x27;none&#x27;</span> <span class="attr">io</span>=<span class="string">&#x27;native&#x27;</span>/&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">source</span> <span class="attr">dev</span>=<span class="string">&#x27;/dev/disk/by-id/ata-TOSHIBA&#x27;</span> <span class="attr">index</span>=<span class="string">&#x27;2&#x27;</span>/&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">target</span> <span class="attr">dev</span>=<span class="string">&#x27;sdg&#x27;</span> <span class="attr">bus</span>=<span class="string">&#x27;scsi&#x27;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">disk</span>&gt;</span></span><br></pre></td></tr></table></figure><p>硬盘直通（virtio），samba分享时可能卡顿</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;disk type<span class="operator">=</span><span class="string">&quot;block&quot;</span> device<span class="operator">=</span><span class="string">&quot;disk&quot;</span>&gt;</span><br><span class="line">  &lt;driver name<span class="operator">=</span><span class="string">&quot;qemu&quot;</span> type<span class="operator">=</span><span class="string">&quot;raw&quot;</span> cache<span class="operator">=</span><span class="string">&quot;none&quot;</span>/&gt;</span><br><span class="line">  &lt;source dev<span class="operator">=</span><span class="string">&quot;/dev/disk/by-id/ata-ST3000DM008&quot;</span>/&gt;</span><br><span class="line">  &lt;target dev<span class="operator">=</span><span class="string">&quot;sdc&quot;</span> bus<span class="operator">=</span><span class="string">&quot;virtio&quot;</span>/&gt;</span><br><span class="line">&lt;/disk&gt;</span><br></pre></td></tr></table></figure><p>无线网口不支持桥接，使用路由绕过</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 修改 /etc/sysctl.conf; sudo sysctl -p 生效</span></span><br><span class="line">net.ipv4.ip_forward = 1</span><br><span class="line">net.ipv4.conf.wlo1.proxy_arp=1</span><br><span class="line">net.ipv4.conf.br-routed.proxy_arp=1</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建网桥和路由</span></span><br><span class="line"><span class="comment">#/etc/systemd/system/bridge-routed.service</span></span><br><span class="line">[Unit]</span><br><span class="line">Description=Create routed bridge br-routed</span><br><span class="line">After=network.target</span><br><span class="line">Wants=network.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=oneshot</span><br><span class="line">RemainAfterExit=<span class="built_in">yes</span></span><br><span class="line">ExecStartPre=/usr/bin/ip <span class="built_in">link</span> add br-routed <span class="built_in">type</span> bridge</span><br><span class="line">ExecStart=/usr/bin/ip <span class="built_in">link</span> <span class="built_in">set</span> br-routed up</span><br><span class="line">ExecStartPost=/usr/bin/ip route add 192.168.1.41/32 dev br-routed</span><br><span class="line">ExecStop=/usr/bin/ip <span class="built_in">link</span> delete br-routed</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改 kvm 配置</span></span><br><span class="line">&lt;interface <span class="built_in">type</span>=<span class="string">&quot;bridge&quot;</span>&gt;</span><br><span class="line">  &lt;mac address=<span class="string">&quot;00:00:00:00:00:00&quot;</span>/&gt;</span><br><span class="line">  &lt;<span class="built_in">source</span> bridge=<span class="string">&quot;br-routed&quot;</span>/&gt;</span><br><span class="line">  &lt;target dev=<span class="string">&quot;vnet0&quot;</span>/&gt;</span><br><span class="line">  &lt;model <span class="built_in">type</span>=<span class="string">&quot;virtio&quot;</span>/&gt;</span><br><span class="line">  &lt;<span class="built_in">alias</span> name=<span class="string">&quot;net0&quot;</span>/&gt;</span><br><span class="line">  &lt;address <span class="built_in">type</span>=<span class="string">&quot;pci&quot;</span> domain=<span class="string">&quot;0x0000&quot;</span> bus=<span class="string">&quot;0x0a&quot;</span> slot=<span class="string">&quot;0x00&quot;</span> <span class="keyword">function</span>=<span class="string">&quot;0x0&quot;</span>/&gt;</span><br><span class="line">&lt;/interface&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 虚拟机使用静态ip</span></span><br><span class="line">   IPv4 地址 . . . . . . . . . . . . : 192.168.1.41(首选)</span><br><span class="line">   子网掩码  . . . . . . . . . . . . : 255.255.255.0</span><br><span class="line">   默认网关. . . . . . . . . . . . . : 192.168.1.1</span><br><span class="line">   DNS 服务器  . . . . . . . . . . . : 192.168.1.1</span><br></pre></td></tr></table></figure><h3 id="dolphin-右键菜单自定义快捷方式"><a href="#dolphin-右键菜单自定义快捷方式" class="headerlink" title="dolphin 右键菜单自定义快捷方式"></a>dolphin 右键菜单自定义快捷方式</h3><p>支持文件和文件夹</p><p>chmod +  ~&#x2F;.local&#x2F;share&#x2F;kio&#x2F;servicemenus&#x2F;mklink.desktop</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[Desktop Entry]</span></span><br><span class="line"><span class="attr">Type</span>=Service</span><br><span class="line"><span class="attr">ServiceTypes</span>=KonqPopupMenu/Plugin</span><br><span class="line"><span class="attr">MimeType</span>=inode/directory<span class="comment">;application/octet-stream;</span></span><br><span class="line"><span class="attr">Actions</span>=RunMyScript</span><br><span class="line"></span><br><span class="line"><span class="section">[Desktop Action RunMyScript]</span></span><br><span class="line"><span class="attr">Name</span>=制作符号链接</span><br><span class="line"><span class="attr">Icon</span>=emblem-symbolic-link</span><br><span class="line"><span class="attr">Exec</span>=/home/rlj/scripts/mklink.sh <span class="string">&quot;%F&quot;</span></span><br></pre></td></tr></table></figure><p>mklink.sh</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"></span><br><span class="line">NAME=$(<span class="built_in">basename</span> <span class="string">&quot;<span class="variable">$1</span>&quot;</span>)</span><br><span class="line"><span class="built_in">ln</span> -s <span class="string">&quot;<span class="variable">$NAME</span>&quot;</span> <span class="string">&quot;<span class="variable">$NAME</span>.lnk&quot;</span></span><br></pre></td></tr></table></figure><h3 id="haruna-视频播放器"><a href="#haruna-视频播放器" class="headerlink" title="haruna 视频播放器"></a>haruna 视频播放器</h3><p>haruna 是 mpv 播放器的前端，功能比较齐全</p><p>存在的问题:</p><ul><li>视频播放器首次最大化窗口，总是跳到另一个屏幕<ul><li>关闭另一个屏幕，关闭应用，再打开，再最大化，再重新打开屏幕</li></ul></li><li>无法重命名文件<ul><li>设置 flatpak 目录权限</li></ul></li></ul><h3 id="flatpak-使用"><a href="#flatpak-使用" class="headerlink" title="flatpak 使用"></a>flatpak 使用</h3><figure class="highlight jboss-cli"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 换源</span></span><br><span class="line">flatpak remote-modify flathub <span class="params">--url=https</span>:<span class="string">//mirrors.ustc.edu.cn/flathub</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 目录授权，解决应用无法编辑文件</span></span><br><span class="line">flatpak override <span class="params">--user</span> org.kde.haruna <span class="params">--filesystem=/share</span></span><br></pre></td></tr></table></figure><h3 id="挂载-SMB-共享支持-windows-符号链接"><a href="#挂载-SMB-共享支持-windows-符号链接" class="headerlink" title="挂载 SMB 共享支持 windows 符号链接"></a>挂载 SMB 共享支持 windows 符号链接</h3><p>支持原生符号链接（目录连接），双向操作都能同步</p><p>步骤</p><ul><li><p>windows 允许符号链接<br>计算机配置 → 管理模板 → 系统 → 文件系统 → 启用 Win32 长路径<br>计算机配置 → windwows设置 → 安全设置 → 本地策略 → 用户权限分配 → 创建符号链接</p></li><li><p>samba 挂载选项<br>symlink&#x3D;native</p></li><li><p>链接时使用相对路径（两个系统绝对路径不一样）</p></li></ul><h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h2><h3 id="故障排查方法"><a href="#故障排查方法" class="headerlink" title="故障排查方法"></a>故障排查方法</h3><ol><li>查看系统日志 journalctl -k -b0</li><li>查看用户日志，比如应用崩溃 journalctl –user -b0</li><li>用 AI 搜索相关问题，如果无法解决再尝试 google 搜索</li></ol><h3 id="edge-无法启动"><a href="#edge-无法启动" class="headerlink" title="edge 无法启动"></a>edge 无法启动</h3><p>edge 异常退出后，有概率无法启动</p><p>报错</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Microsoft</span> Edge 进程似乎正在使用此用户配置。</span><br><span class="line">Microsoft Edge 已锁定此用户配置以防止损坏。如果你确定没有其他进程正在使用此用户配置，可以将其解锁并重新启动 Microsoft Edge。</span><br></pre></td></tr></table></figure><p>处理方法，其他 chromium 内核浏览器可能也类似</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> ~/.config/microsoft-edge/SingletonLock</span><br></pre></td></tr></table></figure><h3 id="steam-玩游戏卡住"><a href="#steam-玩游戏卡住" class="headerlink" title="steam 玩游戏卡住"></a>steam 玩游戏卡住</h3><p>dmesg 信息</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">7</span>月 <span class="number">27</span> <span class="number">10</span>:<span class="number">48</span>:<span class="number">59</span> neon kernel: x86/split lock detection: #AC: CPU <span class="number">0</span>/KVM/<span class="number">88279</span> took a split_lock trap at address: <span class="number">0</span>xfffff80104613be8</span><br><span class="line"><span class="attribute">7</span>月 <span class="number">27</span> <span class="number">10</span>:<span class="number">51</span>:<span class="number">00</span> neon kernel: x86/split lock detection: #AC: CPU <span class="number">2</span>/KVM/<span class="number">88281</span> took a split_lock trap at address: <span class="number">0</span>xfffff8010465701c</span><br><span class="line"><span class="attribute">7</span>月 <span class="number">27</span> <span class="number">10</span>:<span class="number">52</span>:<span class="number">51</span> neon kernel: x86/split lock detection: #AC: CHTTPClientThre/<span class="number">90929</span> took a split_lock trap at address: <span class="number">0</span>xf098fc4f</span><br><span class="line"><span class="attribute">7</span>月 <span class="number">27</span> <span class="number">10</span>:<span class="number">53</span>:<span class="number">07</span> neon kernel: x86/split lock detection: #AC: CPU <span class="number">1</span>/KVM/<span class="number">88280</span> took a split_lock trap at address: <span class="number">0</span>xfffff8010465701c</span><br></pre></td></tr></table></figure><p>解决方法</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 编辑 /etc/default/grub</span></span><br><span class="line">GRUB_CMDLINE_LINUX_DEFAULT=<span class="string">&#x27;quiet splash split_lock_detect=off&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> update-grub</span><br></pre></td></tr></table></figure><h3 id="迁移系统-修复引导"><a href="#迁移系统-修复引导" class="headerlink" title="迁移系统&amp;修复引导"></a>迁移系统&amp;修复引导</h3><ol><li>进入 livecd，使用 Gparted 复制系统到新硬盘</li><li>修改新硬盘分区的 &#x2F;etc&#x2F;fstab 的挂载点 uuid</li><li>重建 grub 引导</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mount /dev/sdaX /mnt/</span><br><span class="line">mount /dev/sda0 /mnt/boot/efi</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> &#123;proc,dev/sys&#125;; <span class="keyword">do</span></span><br><span class="line">  mount --<span class="built_in">bind</span> /<span class="variable">$&#123;i&#125;</span> /mnt/<span class="variable">$&#123;i&#125;</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">chroot</span> /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi</span><br></pre></td></tr></table></figure><h3 id="睡眠唤醒后很卡"><a href="#睡眠唤醒后很卡" class="headerlink" title="睡眠唤醒后很卡"></a>睡眠唤醒后很卡</h3><p>桌面很卡，cpu 频率上不来，只有 800mhz。<br>映泰主板兼容性问题，换主板解决</p><h3 id="终端删除文件到回收站"><a href="#终端删除文件到回收站" class="headerlink" title="终端删除文件到回收站"></a>终端删除文件到回收站</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install trash-cli</span><br><span class="line"><span class="built_in">alias</span> <span class="built_in">rm</span>=trash</span><br></pre></td></tr></table></figure><h3 id="electorn-应用无法启动"><a href="#electorn-应用无法启动" class="headerlink" title="electorn 应用无法启动"></a>electorn 应用无法启动</h3><p>报错 chrome sandbox 相关</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 编辑 /etc/sysctl.conf 添加</span></span><br><span class="line">kernel.<span class="attribute">apparmor_restrict_unprivileged_userns</span>=0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 生效</span></span><br><span class="line">sudo sysctl -p</span><br></pre></td></tr></table></figure><h3 id="登录界面隐藏无关用户"><a href="#登录界面隐藏无关用户" class="headerlink" title="登录界面隐藏无关用户"></a>登录界面隐藏无关用户</h3><p>比如某些用于文件共享的用户</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 修改 /etc/sddm.conf</span></span><br><span class="line"><span class="section">[Autologin]</span></span><br><span class="line"><span class="attr">Session</span>=plasma</span><br><span class="line"></span><br><span class="line"><span class="section">[Users]</span></span><br><span class="line"><span class="attr">HideUsers</span>=data,data-r,data-s</span><br><span class="line"><span class="attr">HideShells</span>=/usr/sbin/nologin,/bin/<span class="literal">false</span></span><br></pre></td></tr></table></figure><h3 id="偶发硬件设备工作不正常"><a href="#偶发硬件设备工作不正常" class="headerlink" title="偶发硬件设备工作不正常"></a>偶发硬件设备工作不正常</h3><p>强制重启后或者系统崩溃后，如果设备不正常，可以尝试重启到 windows 再切回 linux</p><h3 id="应用在启动器里找不到"><a href="#应用在启动器里找不到" class="headerlink" title="应用在启动器里找不到"></a>应用在启动器里找不到</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -f ~/.config/menus/</span><br><span class="line">kbuildsycoca6 --noincremental</span><br></pre></td></tr></table></figure><h3 id="修复睡眠唤醒问题"><a href="#修复睡眠唤醒问题" class="headerlink" title="修复睡眠唤醒问题"></a>修复睡眠唤醒问题</h3><p>睡眠之后可能出现莫名奇妙的唤醒</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看当前唤醒触发器</span></span><br><span class="line"><span class="built_in">cat</span> /proc/acpi/wakeup | grep <span class="built_in">enable</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看触发器的设备路径</span></span><br><span class="line"><span class="keyword">for</span> p <span class="keyword">in</span> /sys/class/wakeup/*/device/power/wakeup; <span class="keyword">do</span></span><br><span class="line">    dev=$(<span class="built_in">dirname</span> $(<span class="built_in">dirname</span> <span class="variable">$p</span>));</span><br><span class="line">    dev_path=$(<span class="built_in">realpath</span> <span class="variable">$dev</span>)</span><br><span class="line">    <span class="built_in">echo</span> <span class="variable">$&#123;dev_path&#125;</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 直接修改是 /proc/acpi/wakeup 是不生效的</span></span><br><span class="line"><span class="comment"># 设置具体设备的方式禁用 rtc 和 AWAC, 位置格式 $&#123;dev_path&#125;/power/wakeup</span></span><br><span class="line"><span class="comment"># 可以将禁用命令配置到 rc.local</span></span><br><span class="line"><span class="built_in">echo</span> disabled | <span class="built_in">tee</span> /sys/devices/platform/rtc_cmos/power/wakeup  <span class="comment"># rtc</span></span><br><span class="line"><span class="built_in">echo</span> disabled | <span class="built_in">tee</span> /sys/devices/platform/rtc_cmos/rtc/rtc0/alarmtimer.0.auto/power/wakeup <span class="comment"># rtc</span></span><br><span class="line"><span class="built_in">echo</span> disabled | <span class="built_in">tee</span> /sys/devices/platform/ACPI000E:00/power/wakeup  <span class="comment"># AWAC</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查是否生效</span></span><br><span class="line"><span class="built_in">cat</span> /proc/acpi/wakeup | grep <span class="built_in">enable</span></span><br></pre></td></tr></table></figure><h3 id="apt-设置代理"><a href="#apt-设置代理" class="headerlink" title="apt 设置代理"></a>apt 设置代理</h3><p>部分 apt ppa 源需要代理，但是国内的镜像源却不需要。<br>所以需要给每一个源单独设置代理的功能</p><p>有两种设置方式</p><ul><li>白名单模式（只给需要的配置）</li><li>黑名单模式（先设置默认代理， 不需要的设置直连）</li></ul><p>设置语法</p><ul><li>设置代理服务器：<code>Acquire::http::Proxy &lt;proxy-server&gt;</code></li><li>设置某个域名的代理：<code>Acquire::http::proxy::&lt;domain&gt; &lt;proxy-server|DIRECT&gt;</code></li></ul><p>示例</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">#  /etc/apt/apt.conf.d/<span class="number">99</span>proxy</span><br><span class="line"></span><br><span class="line"># 给每种协议的源设置代理，一般设置 https::Proxy 就行了</span><br><span class="line">#Acquire::http::Proxy <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span>;</span><br><span class="line">#Acquire::https::Proxy <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span>;</span><br><span class="line">#Acquire::ftp::Proxy <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span>;</span><br><span class="line">#Acquire::socks::Proxy <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span>;</span><br><span class="line"></span><br><span class="line"># 设置具体域名是否走代理</span><br><span class="line">#Acquire::http::proxy::local.mirror.address <span class="string">&quot;DIRECT&quot;</span>;</span><br><span class="line">#Acquire::http::proxy::HOST_NAME_TO_BE_PROXIED <span class="string">&quot;http://yourproxyaddress:proxyport&quot;</span></span><br><span class="line"></span><br><span class="line"># 黑名单模式示例</span><br><span class="line">Acquire::https::Proxy <span class="string">&quot;http://localhost:10808&quot;</span>;</span><br><span class="line">Acquire::https::Proxy::<span class="string">&quot;mirrors.aliyun.com&quot;</span> <span class="string">&quot;DIRECT&quot;</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="限制硬盘读写速度"><a href="#限制硬盘读写速度" class="headerlink" title="限制硬盘读写速度"></a>限制硬盘读写速度</h3><p>某些nvme硬盘的发热很严重, 如果全速运行会导致系统不稳定.<br>所以需要对硬盘速度进行限制</p><p>使用 systemd 设置限速</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">创建配置目录</span></span><br><span class="line">sudo mkdir -p mkdir -p /etc/systemd/system/&#123;user,system&#125;.slice.d/</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置用户限制</span></span><br><span class="line">sudo tee /etc/systemd/system/user.slice.d/io-limit.conf  &gt; /dev/null &lt;&lt;EOF</span><br><span class="line">[Slice]</span><br><span class="line">IOReadBandwidthMax=/dev/nvme0n1 800M</span><br><span class="line">IOWriteBandwidthMax=/dev/nvme0n1 300M</span><br><span class="line">IOReadIOPSMax=/dev/nvme0n1 60000</span><br><span class="line">IOWriteIOPSMax=/dev/nvme0n1 30000</span><br><span class="line"></span><br><span class="line">EOF</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">系统也使用同样的限制</span></span><br><span class="line">sudo ln -sf /etc/systemd/system/user.slice.d/io-limit.conf \</span><br><span class="line">    /etc/systemd/system/system.slice.d/</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>速度测试</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">~$ </span><span class="language-bash"><span class="built_in">dd</span> <span class="keyword">if</span>=/dev/zero of=testfile bs=1M count=2048 oflag=direct status=progress \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; <span class="built_in">rm</span>  testfile</span></span><br><span class="line">2099249152 bytes (2.1 GB, 2.0 GiB) copied, 7 s, 300 MB/s</span><br><span class="line">2048+0 records in</span><br><span class="line">2048+0 records out</span><br><span class="line">2147483648 bytes (2.1 GB, 2.0 GiB) copied, 7.16429 s, 300 MB/s</span><br></pre></td></tr></table></figure><h3 id="archlinux-命令行不能使用回收站"><a href="#archlinux-命令行不能使用回收站" class="headerlink" title="archlinux 命令行不能使用回收站"></a>archlinux 命令行不能使用回收站</h3><p>安装 cachyos 时，trash-cli 不能使用，其他应用也不能删除文件到回收站</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -S gvfs </span><br></pre></td></tr></table></figure><h3 id="某些软件中输入法不可用"><a href="#某些软件中输入法不可用" class="headerlink" title="某些软件中输入法不可用"></a>某些软件中输入法不可用</h3><p>在使用 fcitx 输入法时，某些软件中无法唤起输入法，无法输入中文（如：微信）<br>通常是因为该程序默认加载了 ibus 模块，只要设置 <code>QT_IM_MODULE=fcitx</code> 环境变量就能解决问题。<br>为了不带来别的影响，仅修改 .desktop 启动文件就行。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p ~/.local/share/applications</span><br><span class="line"></span><br><span class="line">sed &#x27;s|Exec=/usr|Exec=env QT_IM_MODULE=fcitx /usr|&#x27; \</span><br><span class="line">/usr/share/applications/wechat.desktop &gt; ~/.local/share/applications/wechat.desktop</span><br></pre></td></tr></table></figure><h3 id="WPS-每次登录自动启动"><a href="#WPS-每次登录自动启动" class="headerlink" title="WPS 每次登录自动启动"></a>WPS 每次登录自动启动</h3><p>WPS 安装后会在 <code>/etc/xdg/autostart/</code> 放置一个 <code>wps-office-autostart.desktop</code>，桌面环境每次登录都会扫描该目录并执行。它会启动 <code>wpsd</code> 守护进程，预加载 wps、wpp、et 三个组件（即”快速启动”功能）。</p><p>这个机制跟 systemd 无关，所以禁用 systemd 服务不会生效。</p><p>排查过程</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看 XDG 自启动项</span></span><br><span class="line"><span class="built_in">ls</span> /etc/xdg/autostart/ | grep wps</span><br><span class="line"><span class="comment"># wps-office-autostart.desktop</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看启动内容</span></span><br><span class="line"><span class="built_in">cat</span> /etc/xdg/autostart/wps-office-autostart.desktop</span><br><span class="line"><span class="comment"># Exec=/opt/apps/cn.wps.wps-office-pro/files/bin/quickstartoffice start</span></span><br></pre></td></tr></table></figure><p>解决方法：在用户级别覆盖禁用（不影响其他用户，WPS 更新也不会覆盖）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p ~/.config/autostart</span><br><span class="line"><span class="built_in">cp</span> /etc/xdg/autostart/wps-office-autostart.desktop ~/.config/autostart/</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Hidden=true&quot;</span> &gt;&gt; ~/.config/autostart/wps-office-autostart.desktop</span><br></pre></td></tr></table></figure><p><strong>然而，以上配置并没有效果</strong></p><p>其实是 KDE <code>会话恢复</code>机制启动的 wps, KDE 的会话管理基于 X11 的 XSMP（X Session Management Protocol）。<br>保存时机是注销&#x2F;关机时，ksmserver 会向所有注册了 XSMP 的客户端发送 SaveYourself，然后把各客户端返回的 restartCommand 写进 ksmserverrc。</p><p>但问题在于 WPS 的行为比较特殊：即使你在关机前关闭了 WPS 的窗口，wpscloudsvr 和 et 等后台进程可能并没有真正退出。<br>它们会驻留在后台（类似托盘常驻），所以 ksmserver 在保存会话时检测到它们仍在运行，就记录下来了。</p><p>所以：关闭会话恢复（启动为空会话）就行了，我并不需要这个。</p><h3 id="关于-ntfs"><a href="#关于-ntfs" class="headerlink" title="关于 ntfs"></a>关于 ntfs</h3><p>尽量不要在 linux 下使用 ntfs 硬盘，特别是在大量读写的情况下稳定性一般，可能出现掉盘（即使使用 ntfs3 驱动）<br>如果出现 ntfs 硬盘的异常，如掉盘、文件不显示等问题，建议重新启动到 windows 然后运行硬盘的检测修复。</p><h3 id="文件写入磁盘先快后慢"><a href="#文件写入磁盘先快后慢" class="headerlink" title="文件写入磁盘先快后慢"></a>文件写入磁盘先快后慢</h3><p>由于 linux 默认的磁盘缓存策略比较激进，导致文件复制一开始时很快后面变得很慢。<br>特别是内存比较大、或者硬盘速度比较慢（如U盘）时，可能导致后期写入时假死或者写入失败。</p><p>相关的内核参数</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 后台开始回写脏页的内存大小（1G）</span></span><br><span class="line"><span class="attr">vm.dirty_background_bytes</span> = <span class="number">1073741824</span></span><br><span class="line"><span class="comment"># 强制同步回写脏页的内存 (2G)</span></span><br><span class="line"><span class="attr">vm.dirty_bytes</span> = <span class="number">2147483648</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 相应的回写比例，实际值等于：ratio x 内存大小</span></span><br><span class="line"><span class="comment"># 上面的固定值和比例值两种二选一</span></span><br><span class="line"><span class="attr">vm.dirty_background_ratio</span> = <span class="number">2</span></span><br><span class="line"><span class="attr">vm.dirty_ratio</span> = <span class="number">4</span></span><br></pre></td></tr></table></figure><p>一般桌面使用设置 1G&#x2F;2G 够了，而 ubuntu 24 默认是 10%&#x2F;20%，在大内存上有点丧心病狂了。<br>如果主要使用机械硬盘，还可以考虑再调小些。</p><h3 id="修改文件句柄数量限制"><a href="#修改文件句柄数量限制" class="headerlink" title="修改文件句柄数量限制"></a>修改文件句柄数量限制</h3><p>默认限制是 1024, 文件句柄数量太小会导致应用异常，比如 vscode 无法监控文件变更</p><p>修改 &#x2F;etc&#x2F;security&#x2F;limits.conf，在最后追加，然后重启电脑</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">* </span>soft nofile 10240</span><br><span class="line"><span class="bullet">* </span>hard nofile 10240</span><br></pre></td></tr></table></figure><p>如果没有生效，可能需要修改 systemd 配置文件</p><p>编辑 &#x2F;etc&#x2F;systemd&#x2F;system.conf 或者 &#x2F;etc&#x2F;systemd&#x2F;user.conf 文件，找到 DefaultLimitNOFILE 这一行，取消注释并修改：</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">DefaultLimitNOFILE</span>=<span class="number">10240</span>:<span class="number">10240</span></span><br></pre></td></tr></table></figure><figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 重载系统配置</span></span><br><span class="line">sudo systemctl daemon-reload</span><br><span class="line"><span class="comment"># 重载用户配置</span></span><br><span class="line">systemctl --<span class="keyword">user</span> <span class="title">daemon-reload</span></span><br></pre></td></tr></table></figure><h3 id="zram-内存压缩替代-swapfile"><a href="#zram-内存压缩替代-swapfile" class="headerlink" title="zram 内存压缩替代 swapfile"></a>zram 内存压缩替代 swapfile</h3><p>在大内存的场景，swapfile 就没啥必要了。</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装</span></span><br><span class="line">sudo apt install zram-tools</span><br><span class="line"></span><br><span class="line"><span class="comment"># 编辑 /etc/default/zramswap，添加</span></span><br><span class="line"><span class="attribute">ALGO</span>=zstd</span><br><span class="line"><span class="attribute">PERCENT</span>=10</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重启服务</span></span><br><span class="line">sudo systemctl restart zramswap.service</span><br><span class="line"></span><br><span class="line"><span class="comment">#（可选）关闭 swapfile ，从fstab 中注释 swapfile 条目</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#（可选）内存充足的情况，降低内存压缩的优先级</span></span><br><span class="line"><span class="comment"># 修改 sysctl.conf, 追加以下配置</span></span><br><span class="line">vm.<span class="attribute">swappiness</span>=10</span><br></pre></td></tr></table></figure><h3 id="使用-pe-后-linux-无法启动"><a href="#使用-pe-后-linux-无法启动" class="headerlink" title="使用 pe 后 linux 无法启动"></a>使用 pe 后 linux 无法启动</h3><p>由于要更换一个硬盘，所以使用了 pe 里面的 diskgenius 对硬盘进行了克隆（非linux所在硬盘）<br>重启后无法进入 linux 系统, 启动后卡在 initramfs 的 shell。</p><p>进入 pe 的 diskgenius 发现所有 ext4 的分区都提示已损坏。</p><p>进入 linux 的 livecd, 使用 gparted 可以看到所有硬盘的 ext4 分区都有红色的叹号<br>而且分区无法挂载， 使用 <code>dumpe2fs /dev/sda1</code> 会出现以下错误</p><figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dumpe2fs<span class="operator">:</span> Superblock checksum does <span class="built_in">not</span> <span class="keyword">match</span> superblock <span class="keyword">while</span> trying <span class="keyword">to</span> <span class="keyword">open</span> <span class="operator">/</span>dev<span class="operator">/</span>sda1</span><br></pre></td></tr></table></figure><p>修复方法找到损坏的分区，逐个执行 <code>e2fsck</code> 进行修复， 如</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> e2fsck -f -y /dev/sda1</span><br></pre></td></tr></table></figure><p>具体原因还不太清楚，不确定是 pe 的问题还是 diskgenius 的问题。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;这些年日常操作系统一直是 windows 和 macOS 交替使用，Linux 一般只作为服务器的操作系统。&lt;br&gt;然而，咖喱味的 Windows 11 （LTSC）用起来实在难受（平时只玩游戏，下载）&lt;br&gt;arm 的 macbook 虽然能效惊人，但是内存金子价格，软件也封闭（点名finder）和傲慢，实在受不了。&lt;br&gt;最后，还是转向 Linux，毕竟现在 wayland 基本堪用，国产软件也随着信创逐渐丰富了。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Linux" scheme="https://blog.ponder.work/tags/Linux/"/>
    
    <category term="KDE" scheme="https://blog.ponder.work/tags/KDE/"/>
    
  </entry>
  
  <entry>
    <title>在 Linux 虚拟机中使用 PyAutoGUI 做自动化</title>
    <link href="https://blog.ponder.work/2025/05/28/pyautogui-in-linux-virtual-machine/"/>
    <id>https://blog.ponder.work/2025/05/28/pyautogui-in-linux-virtual-machine/</id>
    <published>2025-05-27T21:48:00.000Z</published>
    <updated>2025-06-11T10:11:50.000Z</updated>
    
    <content type="html"><![CDATA[<p>PyAutoGUI 是 GUI 功能强大自动化方案，但 UI 程序的运行环境选择与配置也是一大难题。</p><span id="more"></span><h2 id="系统选择"><a href="#系统选择" class="headerlink" title="系统选择"></a>系统选择</h2><p>为了让环境可迁移，免维护，资源消耗少，必须使用虚拟化的方案。<br>虽然 PyAutoGUI 支持 Windows&#x2F;macOS&#x2F;Linux 三个平台。但是各有各的弊端。</p><ul><li>Windows 的系统臃肿且开发环境不友好</li><li>macOS 的权限管理过于严格且虚拟化难度大，高dpi的屏幕也不适合自动化</li><li>Linux 就是最好的选择了，但执行的应用可能不支持 Linux，必须引入 Wine</li></ul><p>综合以上情况考虑，系统环境选择如下</p><ul><li>pve: 宿主机系统，也可以选择别的环境</li><li>debian: 客户机系统，目前是 debian 12, 方便使用 deepin 的 wine 程序</li><li>mate: 桌面环境，比较轻量，实测兼容性比 xfce 好</li><li><a href="https://gitee.com/spark-store-project/spark-store">星火应用商店</a>: 方便安装各种国内软件和 Wine 程序</li><li>统信 Windows 应用兼容引擎: 在星火应用商店中安装，可以用于安装和打包商店中没有的程序</li></ul><h2 id="虚拟机配置"><a href="#虚拟机配置" class="headerlink" title="虚拟机配置"></a>虚拟机配置</h2><p>步骤</p><ol><li>创建好 pve 虚拟机， 安装 debian，选择 mate 桌面</li><li>设置分辨率：控制中心 -&gt; 显示器 -&gt; 设置为 1920x1080</li><li>关闭自动睡眠：控制中心 -&gt; 电源管理 -&gt; 动作和显示修改为<code>从不</code></li><li>关闭屏幕保护：控制中心 -&gt; 屏幕保护程序 -&gt; 取消勾选所有选项</li><li>安装<code>gnome-screenshot</code>:  用于 PyAutoGUI 内部调用截图</li><li>安装星火应用商店（可选）</li><li>安装统信 Windows 应用兼容引擎（可选）：在星火应用商店中安装</li><li>火焰截图（可选）：在星火应用商店中安装，更方便对应用的按钮和文字截图。</li></ol><p>其他说明：</p><ul><li>如果不关闭自动睡眠和屏幕保护，PyAutoGUI 就无法截取到应用的图像，也就无法操作了。</li><li>图形显示协议默认选 x11，wayland 对显卡有要求，虚拟机不方便处理。</li></ul><h3 id="睡眠后时间不对"><a href="#睡眠后时间不对" class="headerlink" title="睡眠后时间不对"></a>睡眠后时间不对</h3><p>虚拟机睡眠唤醒后时间没有更新，不知道为什么没有触发时间更新。<br>目前只能通过强行同步来解决</p><p>在脚本里执行同步命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 和阿里云ntp服务器同步</span></span><br><span class="line"><span class="built_in">sudo</span> ntpdig -S ntp.aliyun.com</span><br></pre></td></tr></table></figure><p>或者，理论上可以通过虚拟机中的 systemd-suspend hook，或者 pve hookscript 来解决（没试成功）</p><h2 id="PyAutoGUI-使用技巧"><a href="#PyAutoGUI-使用技巧" class="headerlink" title="PyAutoGUI 使用技巧"></a>PyAutoGUI 使用技巧</h2><h3 id="远程登录调试"><a href="#远程登录调试" class="headerlink" title="远程登录调试"></a>远程登录调试</h3><p>pve 默认图片控制台不太方便，不能复制粘贴，所以使用远程登录调试<br>由于系统使用 x11 环境，这里使用 xrdp 作为远程服务端。<br>如果以后更新到 wayland，可以使用 <code>gnome-remote-desktop</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install xorgxrdp xrdp</span><br></pre></td></tr></table></figure><h3 id="远程登录时找不到显示器"><a href="#远程登录时找不到显示器" class="headerlink" title="远程登录时找不到显示器"></a>远程登录时找不到显示器</h3><p>xrdp 或者 ssh 远程登录执行脚本时，窗口相关命令可能会提示找不到显示器。<br>可以在自动化脚本中设置以下环境变量</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">os<span class="selector-class">.environ</span><span class="selector-attr">[<span class="string">&#x27;DISPLAY&#x27;</span>]</span> = <span class="string">&#x27;:0&#x27;</span></span><br><span class="line">os<span class="selector-class">.environ</span><span class="selector-attr">[<span class="string">&#x27;XAUTHORITY&#x27;</span>]</span> = <span class="string">&#x27;/home/&lt;your-name&gt;/.Xauthority&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="过滤没必要的截图日志"><a href="#过滤没必要的截图日志" class="headerlink" title="过滤没必要的截图日志"></a>过滤没必要的截图日志</h3><p>gnome-screenshot 截图时会产生一些无用日志</p><figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="operator">**</span> Message<span class="operator">:</span> 17<span class="operator">:</span>36<span class="operator">:</span>31.888<span class="operator">:</span> Unable <span class="keyword">to</span> <span class="keyword">use</span> GNOME Shell&#x27;s </span><br><span class="line">builtin screenshot <span class="keyword">interface</span>, resorting <span class="keyword">to</span> fallback X11.</span><br></pre></td></tr></table></figure><p>新建一个 <code>gnome-screenshot</code> 文件，赋予执行权限，放到 PATH 环境变量中比 <code>/usr/bin</code> 靠前的路径</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">exec</span> /usr/bin/gnome-screenshot <span class="string">&quot;<span class="variable">$@</span>&quot;</span> &gt;&gt; /tmp/gnome-screenshot.log 2&gt;&amp;1</span><br></pre></td></tr></table></figure><h3 id="验证码识别"><a href="#验证码识别" class="headerlink" title="验证码识别"></a>验证码识别</h3><h4 id="pytesseract"><a href="#pytesseract" class="headerlink" title="pytesseract"></a>pytesseract</h4><p>常见的方案是 <code>pytesseract</code>， 但是效果不好，识别率比较一般。</p><p>安装</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo apt <span class="keyword">install</span> tesseract-ocr</span><br><span class="line">pip <span class="keyword">install</span> pytesseract</span><br></pre></td></tr></table></figure><p>使用</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pytesseract.image_to_string(image, config=<span class="string">&#x27;--psm 8 -c tessedit_char_whitelist=0123456789&#x27;</span>)</span><br></pre></td></tr></table></figure><h4 id="ddddocr"><a href="#ddddocr" class="headerlink" title="ddddocr"></a>ddddocr</h4><p><a href="https://github.com/sml2h3/ddddocr">ddddocr</a> 是基于机器学习的验证码识别库，识别效果比较好。</p><p>这里使用 docker 安装 <a href="https://github.com/sml2h3/ddddocr-fastapi">fastapi接口</a></p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">docker</span> run -d -p <span class="number">8000</span>:<span class="number">8000</span> oozzbb/ddddocr-fastapi:latest</span><br></pre></td></tr></table></figure><p>使用</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">ocr_image</span>(<span class="params">file=<span class="string">&#x27;region.png&#x27;</span></span>):</span><br><span class="line">    url = <span class="string">&#x27;http://your-address:8000/ocr&#x27;</span></span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(file, <span class="string">&#x27;rb&#x27;</span>) <span class="keyword">as</span> fp:</span><br><span class="line">        files = &#123;</span><br><span class="line">            <span class="string">&#x27;file&#x27;</span>: fp,</span><br><span class="line">        &#125;</span><br><span class="line">    data = &#123;</span><br><span class="line">        <span class="string">&#x27;probability&#x27;</span>: <span class="string">&#x27;false&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;png_fix&#x27;</span>: <span class="string">&#x27;false&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;charsets&#x27;</span>: <span class="string">&#x27;0123456789&#x27;</span>  <span class="comment"># 验证码可能的字符列表</span></span><br><span class="line">    &#125;</span><br><span class="line">    response = requests.post(url, files=files, data=data)</span><br><span class="line">    <span class="keyword">assert</span> response.ok</span><br><span class="line">    <span class="keyword">return</span> response.json()[<span class="string">&#x27;data&#x27;</span>]</span><br></pre></td></tr></table></figure><h3 id="高效执行自动化脚本"><a href="#高效执行自动化脚本" class="headerlink" title="高效执行自动化脚本"></a>高效执行自动化脚本</h3><p>可以在宿主机远程调用自动化脚本，并且脚本执行完成后挂起虚拟机</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">HOST=your-name@vm-ip-address</span><br><span class="line">VMID=109   <span class="comment"># pve 虚拟机id</span></span><br><span class="line"></span><br><span class="line">qm resume <span class="variable">$VMID</span>   <span class="comment"># 唤醒虚拟机</span></span><br><span class="line"><span class="built_in">sleep</span> 2</span><br><span class="line"></span><br><span class="line">ssh <span class="variable">$HOST</span> <span class="string">&quot;SOME_VAR=foobar python your-script.py&quot;</span>  <span class="comment"># 执行脚本</span></span><br><span class="line"><span class="built_in">sleep</span> 1</span><br><span class="line"></span><br><span class="line">qm <span class="built_in">suspend</span> <span class="variable">$VMID</span>   <span class="comment"># 关闭虚拟机</span></span><br></pre></td></tr></table></figure><h3 id="其他有用的函数"><a href="#其他有用的函数" class="headerlink" title="其他有用的函数"></a>其他有用的函数</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 切换窗口到前台</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">wm_to_front</span>(<span class="params">title: <span class="built_in">str</span></span>):</span><br><span class="line">    <span class="keyword">if</span> sys.platform == <span class="string">&#x27;linux&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> subprocess.check_call([<span class="string">&#x27;wmctrl&#x27;</span>, <span class="string">&#x27;-a&#x27;</span>, title])</span><br><span class="line">    <span class="keyword">raise</span> NotImplementedError</span><br><span class="line"></span><br><span class="line"><span class="comment"># 点击或者移到到图片位置</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">click_image</span>(<span class="params">image, confidence=<span class="number">0.9</span>, grayscale=<span class="literal">True</span>, delay=<span class="number">1.5</span>, </span></span><br><span class="line"><span class="params">        duration=<span class="number">0.6</span>, offset=(<span class="params"><span class="number">1</span>, <span class="number">0</span></span>), abort=<span class="literal">True</span>, click=<span class="literal">True</span></span>):</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        loc = pyautogui.locateOnScreen(image, confidence=confidence, grayscale=grayscale)</span><br><span class="line">    <span class="keyword">except</span> pyautogui.ImageNotFoundException:</span><br><span class="line">        loc = <span class="literal">None</span></span><br><span class="line">    <span class="keyword">if</span> loc:</span><br><span class="line">        x, y = pyautogui.center(loc)</span><br><span class="line">        <span class="keyword">if</span> click:</span><br><span class="line">            pyautogui.click(x+offset[<span class="number">0</span>], y+offset[<span class="number">1</span>], duration=duration)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            pyautogui.moveTo(x+offset[<span class="number">0</span>], y+offset[<span class="number">1</span>], duration=duration)</span><br><span class="line">        time.sleep(delay)</span><br><span class="line">        <span class="keyword">return</span> loc</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">if</span> abort:</span><br><span class="line">            <span class="keyword">raise</span> RuntimeError(<span class="string">f&quot;can not find <span class="subst">&#123;image&#125;</span>!&quot;</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 等待某个图片出现</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">wait_button</span>(<span class="params">image, timeout, interval=<span class="number">10</span></span>):</span><br><span class="line">    <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(timeout//interval):</span><br><span class="line">        time.sleep(interval)</span><br><span class="line">        loc = click_image(image, abort=<span class="literal">False</span>)</span><br><span class="line">        <span class="keyword">if</span> loc:</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">raise</span> RuntimeError(<span class="string">f&#x27;wait <span class="subst">&#123;image&#125;</span> failed&#x27;</span>)</span><br><span class="line">    time.sleep(interval/<span class="number">2</span>)</span><br></pre></td></tr></table></figure><h2 id="使用感受"><a href="#使用感受" class="headerlink" title="使用感受"></a>使用感受</h2><p>PyAutoGUI 是基于图像而不是图型控件来识别目标，没有确认反馈。<br>相比于网页自动化的 Selenium 就感觉落后些了。<br>当然，Windows 平台有 <code>pywinauto</code> 支持控件识别，但我不咋熟悉。<br>总之，PyAutoGUI 就像手枪，虽然比较简陋，但还是很直观的，执行简单的任务也够用了。</p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul><li>自动化代码中不要编码用户名密码相关信息，建议用环境变量传入</li><li>不要轻易调整系统的 dpi(建议设为1.0) 和分辨率，可能导致图片无法识别</li><li>xfce 对 wine 应用的兼容性似乎不好，wine 文件保存对话框被可能被反复触发，原因不明。</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;PyAutoGUI 是 GUI 功能强大自动化方案，但 UI 程序的运行环境选择与配置也是一大难题。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Python" scheme="https://blog.ponder.work/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>语言的力量</title>
    <link href="https://blog.ponder.work/2025/05/28/the-power-of-language/"/>
    <id>https://blog.ponder.work/2025/05/28/the-power-of-language/</id>
    <published>2025-05-27T20:41:00.000Z</published>
    <updated>2025-05-28T17:08:08.000Z</updated>
    
    <content type="html"><![CDATA[<p>语言的力量远比想象的强大，某种程度上是有虚空造物的能力。</p><span id="more"></span><p>你可以凭空创造一个概念，并植入你的听众脑子，不论好还是坏。诈骗分子、PUA 大师、营销大师无不精通此等技艺。</p><p>起因是一个综艺节目，有个导演对哈妮克孜说了句话，让我惊为天人，让我惊叹“你他娘真是个人才”。<br>它在线下拍戏的时候见到了哈妮克孜，哈当时戴了副大镜框眼镜，于是它说道“我当时看到你的时候，感觉好失望，你和我银幕上看到的哈妮克孜完全不一样”。<br>它凭空创造了一个观念（事实），哈妮克孜颜值不行。怎么创造的呢？和哈妮克孜荧幕形象相比。这显然是不客观的，但很难瞬间就察觉到。</p><p>这样，好与坏、美与丑一切观念都是可以被重新定义的，只要你选择合适的目标和对照标准，甚至是你脑海里的标准。<br>你是丑的和你漂亮的时候比，你是蠢的和聪明的时候比，我对你是失望的和对你的期望相比。<br>PUA 大师就是用这种手法实现打压，新闻学就是这么颠倒黑白，当然也可以反向操作，那么你就成为知心姐姐鼓励大师。</p><p>所以，神话中的言出法随也不难理解，毕竟人是观念的动物，你改变&#x2F;植入了某人的某个观念，那么你就改变了他眼中的世界。<br>这也就是《乔布斯传》中乔布斯的扭曲现实力场。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;语言的力量远比想象的强大，某种程度上是有虚空造物的能力。&lt;/p&gt;</summary>
    
    
    
    <category term="随笔" scheme="https://blog.ponder.work/categories/writing/"/>
    
    
    <category term="随笔" scheme="https://blog.ponder.work/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>联想笔记本 BIOS 跳过检测强制降级</title>
    <link href="https://blog.ponder.work/2025/05/16/levono-bios-force-downgrade/"/>
    <id>https://blog.ponder.work/2025/05/16/levono-bios-force-downgrade/</id>
    <published>2025-05-15T18:00:00.000Z</published>
    <updated>2025-05-15T20:37:15.000Z</updated>
    
    <content type="html"><![CDATA[<p>联想某些版本的 bios 似乎会禁止降级，即使打开 bios 设置里的允许降级选项，依然会提示 “this platform does not support IHISI interface” 的错误，导致降级失败。</p><span id="more"></span><h2 id="降级方法"><a href="#降级方法" class="headerlink" title="降级方法"></a>降级方法</h2><p>经过一些尝试，找到了方法绕过，以 <code>ThinkBook 14 G3 ACL</code> 机型为例</p><ol><li>打开驱动<a href="https://newthink.lenovo.com.cn/driveList.html?selname=21A2004H0D">下载页面</a> </li><li>打开 <a href="https://think.lenovo.com.cn/support/driver/driverdetail.aspx?DEditid=134398">BIOS</a> 页面， 下载以下安装文件<ul><li>当前版本 BIOS 的安装包（<code>NEW</code>），版本 GQCN41WW_HFCN36WW</li><li>以及想要降级的 BIOS (<code>OLD</code>)，版本 GQCN36WW_HFCN31WW</li></ul></li><li>运行 <code>NEW</code>，选择仅解压。找到解压目录，应该只有一个 exe 程序，使用 7-zip 或者 bandizip 打开并提取内部文件。</li><li>对 <code>OLD</code> 也执行如上操作，如果没有解压选项，可以尝试直接提取。</li><li>将 <code>OLD</code> 提取目录中的 .bin 格式固件（GLV3A036.bin、GLV4D031.bin）复制到 <code>NEW</code> 提取目录中。</li><li>修改 <code>NEW</code> 提取目录中的 <code>platform.ini</code> 文件，找到如下内容。将其中的 .bin 文件名，修改为 <code>OLD</code> 中的 .bin 文件名，保存。有两个 .bin 文件，文件名是和版本中的数字对应的，不要修改错误。 <figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">; 修改前</span><br><span class="line">[MULTI_FD]</span><br><span class="line"><span class="attribute">Flag</span>=1</span><br><span class="line">FD#<span class="attribute">01</span>=ID,GLV4D,GLV4D036.bin</span><br><span class="line">FD#<span class="attribute">02</span>=PCI,0,1,1,0,FFFFFFFF,FFFFFFFF,GLV3A041.bin</span><br><span class="line"></span><br><span class="line">; 修改后</span><br><span class="line">[MULTI_FD]</span><br><span class="line"><span class="attribute">Flag</span>=1</span><br><span class="line">FD#<span class="attribute">01</span>=ID,GLV4D,GLV4D031.bin</span><br><span class="line">FD#<span class="attribute">02</span>=PCI,0,1,1,0,FFFFFFFF,FFFFFFFF,GLV3A036.bin</span><br></pre></td></tr></table></figure></li><li>运行 <code>NEW</code> 提取目录中的 H2OFFT-Wx64.exe，等待降级完成。</li></ol><p><strong>注意：有风险，请谨慎操作。不要跨太多版本，可能有不可预料的问题</strong></p><p><img data-src="https://image.ponder.work/mweb/2025-05-15---17473119676089.jpg"></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.geektech.co.nz/lenovo-y700-how-to-enable-bios-downgrade">https://www.geektech.co.nz/lenovo-y700-how-to-enable-bios-downgrade</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;联想某些版本的 bios 似乎会禁止降级，即使打开 bios 设置里的允许降级选项，依然会提示 “this platform does not support IHISI interface” 的错误，导致降级失败。&lt;/p&gt;</summary>
    
    
    
    <category term="工作生活" scheme="https://blog.ponder.work/categories/life/"/>
    
    
    <category term="硬件" scheme="https://blog.ponder.work/tags/%E7%A1%AC%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>redroid “设备未获得play保护机制认证” 问题</title>
    <link href="https://blog.ponder.work/2025/05/08/redroid-google-play-login/"/>
    <id>https://blog.ponder.work/2025/05/08/redroid-google-play-login/</id>
    <published>2025-05-07T18:00:00.000Z</published>
    <updated>2025-05-08T15:05:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>使用 redroid 等安卓虚拟环境，可能会发现 google play 用不了的问题。<br>虽然系统集成了 gapps，但系统提示 “设备未获得play保护机制认证”，无法登录 play 商店。<br>可能的原因比较多，这里大概是因为虚拟机的型号没在google的数据库里。<br>解决方案就是，获取 GSF ID 注册到 Google。</p><span id="more"></span><p><img data-src="https://image.ponder.work/mweb/2025-05-08---17466861942743.jpg"></p><h2 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h2><ol><li>安装 <a href="https://apkpure.com/device-id/vtechnotm.com.deviceid">device id</a></li><li>打开应用，复制 GSF id，假设为<code>ffffffff</code></li><li>终端运行<code>printf &quot;%d\n&quot; 0xffffffff</code>，转换换成10进制数字。</li><li>打开 <a href="https://www.google.com/android/uncertified/">https://www.google.com/android/uncertified/</a> 输入转换后的结果并提交</li></ol><p><img data-src="https://image.ponder.work/mweb/2025-05-08---17466877549957.jpg"> </p>]]></content>
    
    
    <summary type="html">&lt;p&gt;使用 redroid 等安卓虚拟环境，可能会发现 google play 用不了的问题。&lt;br&gt;虽然系统集成了 gapps，但系统提示 “设备未获得play保护机制认证”，无法登录 play 商店。&lt;br&gt;可能的原因比较多，这里大概是因为虚拟机的型号没在google的数据库里。&lt;br&gt;解决方案就是，获取 GSF ID 注册到 Google。&lt;/p&gt;</summary>
    
    
    
    <category term="工作生活" scheme="https://blog.ponder.work/categories/life/"/>
    
    
    <category term="Android" scheme="https://blog.ponder.work/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>在 iOS 上访问安卓应用</title>
    <link href="https://blog.ponder.work/2025/04/27/access-android-apps-on-ios/"/>
    <id>https://blog.ponder.work/2025/04/27/access-android-apps-on-ios/</id>
    <published>2025-04-26T18:00:00.000Z</published>
    <updated>2025-04-27T14:53:17.000Z</updated>
    
    <content type="html"><![CDATA[<p>有些应用在安卓上是独占的，iOS 上又没有比较好的替代品，而且 iOS 上没有能用安卓模拟器。<br>如果使用多个设备，维护的心智成本又高，被这个问题困扰了许久。</p><p>最近碰巧了解了 <a href="https://github.com/Genymobile/scrcpy">scrcpy</a>, 用于远程控制安卓，终于解决了这个问题。</p><span id="more"></span><p>需要的工具</p><ol><li>安卓环境：虚拟环境或者物理设备均可，这里使用虚拟环境。以下是可选的虚拟环境</li></ol><ul><li>PVE 的 PCT容器 (Proxmox Container Toolkit)，需要打<a href="https://github.com/lurenJBD/PCT-patches">补丁</a></li><li>docker，基于<a href="https://github.com/remote-android/redroid-doc">Redroid</a> 需要调整内核参数</li><li>WSA (Windows Subsystem for Android)，与 scrcpy 配合有点问题，窗口有黑边。</li><li>VMware、VirtualBox等虚拟化平台，需要解决虚拟显卡。</li></ul><ol start="2"><li>使用 <a href="https://github.com/wsvn53/scrcpy-mobile">scrcpy-mobile</a> 远程控制安卓环境, 当然 scrcpy 也支持 Windows、Linux、macOS</li></ol><h2 id="PCT-安卓容器"><a href="#PCT-安卓容器" class="headerlink" title="PCT 安卓容器"></a>PCT 安卓容器</h2><p>由于已有PVE环境，这里选用PCT，主要步骤</p><ol><li>根据 <a href="https://github.com/lurenJBD/PCT-patches">PCT-patches</a> 文档，给 PCT 打好补丁</li><li>根据 <a href="https://github.com/lurenJBD/PCT-patches/releases/download/lineage/lineage19.1-x86_64-houdini-magisk-gapps.tar.gz">lineageOS 模板</a>，新建安卓容器，注意去除Unprivileged container的勾选。</li><li>修改 lxc.init.cmd 选项，在后面增加以下参数，调整分辨率和iPad一致。 <figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">androidboot.<span class="attribute">redroid_width</span>=1668 androidboot.<span class="attribute">redroid_height</span>=2388 androidboot.<span class="attribute">redroid_fps</span>=60</span><br></pre></td></tr></table></figure></li></ol><p>ps: 安卓容器对宿主机性能似乎有一定要求，J4125 只是勉强够用，流畅度一般。</p><h2 id="macOS-中使用-scrcpy"><a href="#macOS-中使用-scrcpy" class="headerlink" title="macOS 中使用 scrcpy"></a>macOS 中使用 scrcpy</h2><p>安装 <a href="https://github.com/Genymobile/scrcpy">scrcpy</a> ，连接命令</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">scrcpy <span class="attribute">--audio-codec</span>=aac \</span><br><span class="line">  <span class="attribute">--video-codec</span>=h264 \</span><br><span class="line">  <span class="attribute">--video-bit-rate</span>=16M \</span><br><span class="line">  <span class="attribute">--max-fps</span>=60 \</span><br><span class="line">  <span class="attribute">--tcpip</span>=192.168.10.181:5555 \</span><br><span class="line">  <span class="attribute">--start-app</span>=io.legado.app.release</span><br></pre></td></tr></table></figure><p>参数解释</p><ul><li><code>--audio-codec=aac</code>同步声音，使用 AAC 编码，默认参数可能导致没有声音</li><li><code>--video-codec=h264</code>使用 H.264 视频压缩编码</li><li><code>--video-bit-rate=16M</code>16Mbps 码率，高画质</li><li><code>--max-fps=60</code>最大帧率 60FPS，画面流畅</li><li><code>--tcpip=192.168.10.181:5555</code>Wi-Fi adb 连接设备</li><li><code>--start-app=io.legado.app.release</code> （可选）连接后直接启动 Legado App，应用列表可使用<code>adb shell pm list packages -3</code>查看</li></ul><h2 id="iOS-中使用-scrcpy-mobile"><a href="#iOS-中使用-scrcpy-mobile" class="headerlink" title="iOS 中使用 scrcpy-mobile"></a>iOS 中使用 scrcpy-mobile</h2><p>appstore中安装 <a href="https://apps.apple.com/cn/app/scrcpy-remote/id1629352527">scrcpy-mobile</a></p><p>由于该应用的用户界面易用性比较差，表单也不支持有些 scrcpy 参数，这里直接使用<code>快捷指令</code>打开应用。<br>设置打开 scrcpy-mobile 后，开启<code>引导式访问</code>，防止误触。同时可以在 iOS 设置中开启引导式访问的<code>面容id</code>，防止频繁输入密码。</p><p><img data-src="https://image.ponder.work/mweb/2025-04-27---17457324901465.jpg"></p><p>scrcpy-mobile 的 url schema</p><figure class="highlight sas"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scrcpy2://192.168.10.181:5555?enable-audio=true<span class="variable">&amp;audio</span>-codec=aac<span class="variable">&amp;video</span>-bit-rate=16M<span class="variable">&amp;video</span>-codec=h264<span class="variable">&amp;max</span>-fps=60</span><br></pre></td></tr></table></figure><p>最后，这也未尝不算一种 NTR<br><img data-src="https://image.ponder.work/mweb/2025-04-27---IMG_202504275684_512x682.jpg" alt="IMG_202504275684_512x682"></p><h2 id="安卓环境设置（可选）"><a href="#安卓环境设置（可选）" class="headerlink" title="安卓环境设置（可选）"></a>安卓环境设置（可选）</h2><ol><li>关闭导航栏：系统 &gt; 手势 &gt; 系统导航 &gt; 手势导航</li><li>加快或者关闭系统动画：开发者选项 &gt; 窗口动画缩放、过渡动画缩放、Animator 时长比例 &gt; 关闭或者0.5x</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;有些应用在安卓上是独占的，iOS 上又没有比较好的替代品，而且 iOS 上没有能用安卓模拟器。&lt;br&gt;如果使用多个设备，维护的心智成本又高，被这个问题困扰了许久。&lt;/p&gt;
&lt;p&gt;最近碰巧了解了 &lt;a href=&quot;https://github.com/Genymobile/scrcpy&quot;&gt;scrcpy&lt;/a&gt;, 用于远程控制安卓，终于解决了这个问题。&lt;/p&gt;</summary>
    
    
    
    <category term="工作生活" scheme="https://blog.ponder.work/categories/life/"/>
    
    
    <category term="Android" scheme="https://blog.ponder.work/tags/Android/"/>
    
    <category term="iOS" scheme="https://blog.ponder.work/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>在 VSCode 中用 Rust 刷LeetCode</title>
    <link href="https://blog.ponder.work/2025/03/09/vscode-leetcode-rust/"/>
    <id>https://blog.ponder.work/2025/03/09/vscode-leetcode-rust/</id>
    <published>2025-03-08T20:17:00.000Z</published>
    <updated>2025-03-16T15:18:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍在 VSCode 中配置和使用插件来高效地解决 LeetCode 问题，并使用 Rust 语言编写和测试代码。</p><span id="more"></span><h2 id="vscode-插件"><a href="#vscode-插件" class="headerlink" title="vscode 插件"></a>vscode 插件</h2><ul><li>LeetCode.vscode-leetcode</li><li>pucelle.run-on-save</li><li>rust-lang.rust-analyzer</li></ul><h2 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h2><p>cargo new vscode-leetcode-rust</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tree -I target --dirsfirst</span></span><br><span class="line">.</span><br><span class="line">├── Cargo<span class="selector-class">.lock</span></span><br><span class="line">├── Cargo<span class="selector-class">.toml</span></span><br><span class="line">└── <span class="attribute">src</span></span><br><span class="line">    ├── lib<span class="selector-class">.rs</span></span><br><span class="line">    ├── <span class="selector-tag">main</span><span class="selector-class">.rs</span></span><br><span class="line">    └── solutions</span><br><span class="line">        ├── <span class="number">1</span>_two_sum<span class="selector-class">.rs</span></span><br><span class="line">        ...</span><br></pre></td></tr></table></figure><h2 id="vscode-全局设置"><a href="#vscode-全局设置" class="headerlink" title="vscode 全局设置"></a>vscode 全局设置</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;leetcode.useEndpointTranslation&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span>  <span class="comment">// for english filename</span></span><br><span class="line"><span class="attr">&quot;leetcode.workspaceFolder&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/Users/&lt;your_name&gt;/projects/vscode-leetcode-cn-rust&quot;</span><span class="punctuation">,</span>   </span><br><span class="line"><span class="attr">&quot;leetcode.filePath&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;default&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;folder&quot;</span><span class="punctuation">:</span> <span class="string">&quot;src/solutions&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;filename&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;id&#125;_$&#123;snake_case_name&#125;.$&#123;ext&#125;&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><h2 id="用-automod-宏添加新回答到模块"><a href="#用-automod-宏添加新回答到模块" class="headerlink" title="用 automod 宏添加新回答到模块"></a>用 automod 宏添加新回答到模块</h2><p>cargo add automod</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/lib.rs </span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> CURRENT: &amp;<span class="type">str</span> = <span class="string">&quot;sdfsdfsd.rs&quot;</span>;  <span class="comment">// trigger rust-analyser recheck</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">mod</span> solutions &#123;</span><br><span class="line">    automod::dir!(<span class="string">&quot;src/solutions&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="触发-rust-analyzer"><a href="#触发-rust-analyzer" class="headerlink" title="触发 rust-analyzer"></a>触发 rust-analyzer</h2><p>用 run-on-save 插件，保存回答时更新 lib.rs，触发 rust-analyzer 重新分析项目，开启新回答的代码补全。</p><p>vscode 项目配置</p><figure class="highlight dart"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;runOnSave.commands&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">         <span class="comment">// use gnu sed update lib.rs when add solutions (macOS)</span></span><br><span class="line">        <span class="string">&quot;command&quot;</span>: <span class="string">&quot;sh onsave.sh <span class="subst">$&#123;fileBasename&#125;</span>&quot;</span>,  </span><br><span class="line">        <span class="string">&quot;runIn&quot;</span>: <span class="string">&quot;backend&quot;</span>,</span><br><span class="line">        <span class="string">&quot;finishStatusMessage&quot;</span>: <span class="string">&quot;touched <span class="subst">$&#123;workspaceFolderBasename&#125;</span>&quot;</span></span><br><span class="line">    &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p><code>onsave.sh</code>脚本，macOS使用 gnused，linux 使用默认 sed 就好。<br>通过切换模块是否为pub来触发 rust-analyzer 识别新回答</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">FILE=<span class="variable">$1</span></span><br><span class="line"><span class="keyword">if</span> [[ `grep <span class="string">&quot;<span class="variable">$FILE</span>&quot;</span> src/lib.rs | <span class="built_in">wc</span> -l` -eq 0 ]]; <span class="keyword">then</span></span><br><span class="line">    gsed -i -E <span class="string">&quot;/CURRENT/c\const CURRENT: &amp;str = \&quot;<span class="variable">$FILE</span>\&quot;;&quot;</span> src/lib.rs</span><br><span class="line">    <span class="keyword">if</span> [[ `grep <span class="string">&#x27;::dir!(pub&#x27;</span> src/lib.rs | <span class="built_in">wc</span> -l` -eq 1 ]]; <span class="keyword">then</span></span><br><span class="line">        gsed -i <span class="string">&quot;s|::dir!(pub |::dir!(|&quot;</span> src/lib.rs</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        gsed -i <span class="string">&quot;s|::dir!(|::dir!(pub |&quot;</span> src/lib.rs</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="编写本地测试用例"><a href="#编写本地测试用例" class="headerlink" title="编写本地测试用例"></a>编写本地测试用例</h2><p>把测试代码写在 <code>&quot;// @lc code=end&quot;</code> 后面，需要定义 Solution 结构体，可能还需要定义参数的结构体。</p><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// <span class="variable">@lc</span> code=<span class="keyword">end</span></span><br><span class="line">struct <span class="title class_">Solution</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">#[test]</span></span><br><span class="line"><span class="keyword">fn</span> test_a() &#123;</span><br><span class="line">    // let res = <span class="title class_">Solution</span>::is_valid(<span class="title class_">String</span>::from(<span class="string">&quot;()[]&#123;&#125;&quot;</span>));</span><br><span class="line">    // println!(<span class="string">&quot;RESUTL\t&#123;:?&#125;&quot;</span>, res);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img data-src="https://image.ponder.work/mweb/2025-03-16---17420958075794.jpg" alt="-w340"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍在 VSCode 中配置和使用插件来高效地解决 LeetCode 问题，并使用 Rust 语言编写和测试代码。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Rust" scheme="https://blog.ponder.work/tags/Rust/"/>
    
  </entry>
  
  <entry>
    <title>跨域的那些事</title>
    <link href="https://blog.ponder.work/2023/08/15/cross-domain-things/"/>
    <id>https://blog.ponder.work/2023/08/15/cross-domain-things/</id>
    <published>2023-08-15T15:00:00.000Z</published>
    <updated>2024-07-12T09:22:00.000Z</updated>
    
    <content type="html"><![CDATA[<ul><li>什么是跨域？<ul><li>就是当前域访问了非本域的资源。对于http来说，url代表资源，也就是访问了非本域的url。</li></ul></li></ul><span id="more"></span><ul><li>域的定义是啥？<ul><li>这里的域，也就是同源策略（Same-origin policy）中的源。</li><li>如果两个 URL 的协议、端口和主机都相同的话，则这两个 URL 是同源的。</li><li>当前域：当前网页的域；目标域：访问的资源所在的域</li></ul></li><li>为什么要限制跨域请求？信息泄露<ul><li>js直接读取浏览器的目标域的sessionid、cookie，伪造请求。<ul><li>比如，当前站点是恶意站点，它用js请求银行站点，盗取信息。</li></ul></li><li>CSRF，如img标签的src会被访问，利用受害者已认证的身份，在不知情的情况下向受害者认证的站点发起恶意请求。<ul><li>通过在src里构造url，恶意请求目标域<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;img <span class="attribute">src</span>=<span class="string">&quot;https://bank.com/transfer?amount=1000&amp;to=attackerAccount&quot;</span> </span><br><span class="line">      <span class="attribute">style</span>=<span class="string">&quot;display:none;&quot;</span>&gt;</span><br></pre></td></tr></table></figure></li></ul></li><li>XSS跨站脚本(Cross-site scripting)注入，导致用户在信息泄露<ul><li>一般是接受了用户构造的输入，输入里包含恶意脚本内容，内容未被转义，又输出在页面上，从而被执行</li><li>其他用户查看了该页面，注入的脚本被执行</li></ul></li></ul></li><li>怎么限制跨域？浏览器的同源策略<ul><li>禁止的<ul><li>DOM 同源策略： 不同源的dom之间不能相互操作，多个iframe的情况</li><li>XMLHttpRequest 同源策略： 禁止请求不同源url</li><li>Cookie、LocalStorage、IndexedDB 等存储性内容同源策略</li></ul></li><li>允许的<ul><li>页面中的链接，重定向以及表单提交</li><li><code>&lt;script&gt;、&lt;img&gt;、&lt;link&gt;</code>这些包含 src 属性的标签可以加载跨域资源。（只能GET）</li></ul></li></ul></li><li>限制跨域导致哪些不便？<ul><li>前后端分离开发时，localhost不能正常访问后端资源</li><li>一些公共的api不能被访问</li><li>https的页面的http的静态资源，不能加载</li></ul></li><li>如何绕过同源策略？<ul><li>浏览器启动参数（在用户端操作）</li><li>反向代理（在当前域操作）</li><li>JSONP（在目标域操作）<ul><li>利用<code>&lt;script&gt;</code>允许跨域的特点，设置标签的src为目标域，动态生成需要的javascript内容</li></ul></li><li>跨源资源共享（CORS）（目标域操作）<ul><li>设置相应的reponse header<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">Access</span>-Control-Allow-Origin: https://foo.example  // 所允许的来源域</span><br><span class="line"><span class="keyword">Access</span>-Control-Allow-Methods: POST, <span class="keyword">GET</span>, <span class="keyword">OPTIONS</span>   // 所允许的请求方法</span><br><span class="line"><span class="keyword">Access</span>-Control-Allow-Headers: X-PINGOTHER, Content-<span class="keyword">Type</span>  // 所允许的请求<span class="keyword">header</span></span><br><span class="line"><span class="keyword">Access</span>-Control-Max-Age: <span class="number">86400</span></span><br></pre></td></tr></table></figure></li></ul></li></ul></li><li>参考<ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy">https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS">https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Glossary/CSRF">https://developer.mozilla.org/zh-CN/docs/Glossary/CSRF</a></li><li><a href="https://juejin.cn/post/6879360544323665928">https://juejin.cn/post/6879360544323665928</a></li><li><a href="https://juejin.cn/post/6867096987804794888">https://juejin.cn/post/6867096987804794888</a></li></ul></li></ul>]]></content>
    
    
    <summary type="html">&lt;ul&gt;
&lt;li&gt;什么是跨域？&lt;ul&gt;
&lt;li&gt;就是当前域访问了非本域的资源。对于http来说，url代表资源，也就是访问了非本域的url。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Web" scheme="https://blog.ponder.work/tags/Web/"/>
    
  </entry>
  
  <entry>
    <title>HomeBrew 与无 root 权限 Linux 环境包管理</title>
    <link href="https://blog.ponder.work/2023/05/29/homebrew-as-non-root-package-manager/"/>
    <id>https://blog.ponder.work/2023/05/29/homebrew-as-non-root-package-manager/</id>
    <published>2023-05-28T20:00:00.000Z</published>
    <updated>2023-05-28T23:49:17.000Z</updated>
    
    <content type="html"><![CDATA[<p>一些公用的 Linux 服务器，处于维护以及安全考虑，一般只会提供普通权限用户给使用者。<br>普通用户的权限满足日常使用是够了，但是难以配置自己的开发环境，安装一些自己需要的包。</p><p>如果都从源码编译安装软件，依赖的维护过于复杂，初始编译工具链的版本可能也不满足需求，如 gcc 版本过低。<br>如果申请 sudo 权限或者请求更新系统或安装 docker，后期责任难以界定，运维和管理员一般也不会同意。</p><p>所以，最优方案还是有需求的用户在个人目录维护自己的工具链和环境。下文方案为围绕 HomeBrew 构建。</p><span id="more"></span><h2 id="安装-miniconda-解决前置依赖"><a href="#安装-miniconda-解决前置依赖" class="headerlink" title="安装 miniconda 解决前置依赖"></a>安装 miniconda 解决前置依赖</h2><p>如果你的系统比较新，可以直接尝试<code>安装 HomeBrew</code>。</p><p>基于上面讨论的内容，公用服务器一般存在系统版本低的问题，是 centos7 或者 centos6 也毫不稀奇，而且如 glibc 等库的版本也非常低。</p><p>安装 HomeBrew 有两个强依赖，git 及 curl，而且依赖的版本都比较高，centos7 的版本也不能满足。<br>另外，由于 Brew 不少软件都需要从源码编译，gcc 和良好的网络环境也不可缺少。</p><p>幸好 miniconda 能够解决以上几点问题。miniconda 只是提供 HomeBrew 安装的依赖，后续可以删除。</p><p>配置 conda 源（可选）： 新建 <code>.condarc</code>，包含以下内容</p><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">channels:</span><br><span class="line">  - defaults</span><br><span class="line">show_channel_urls: true</span><br><span class="line">default_channels:</span><br><span class="line">  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main</span><br><span class="line">  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/<span class="attribute">r</span></span><br><span class="line"><span class="attribute">  - https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2</span></span><br><span class="line"><span class="attribute">custom_channels</span>:</span><br><span class="line">  <span class="attribute">conda-forge</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">msys2</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">bioconda</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">menpo</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">pytorch</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">pytorch-lts</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br><span class="line">  <span class="attribute">simpleitk</span>: <span class="attribute">https</span>:<span class="comment">//mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><br></pre></td></tr></table></figure><p>下载及安装 miniconda</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">下载</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">如果服务器的内置证书已过期， 增加 --no-check-certificate 条件跳过证书验证</span></span><br><span class="line">wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh &amp;&amp; chmod +x Miniconda3-latest-Linux-x86_64.sh</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装</span></span><br><span class="line">./Miniconda3-latest-Linux-x86_64.sh -b -p ~/miniconda3</span><br><span class="line">source ~/miniconda3/etc/profile.d/conda.sh</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装所需包</span></span><br><span class="line">conda install -y gcc_linux-64 gxx_linux-64 curl git</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">链接 gcc，等 HomeBrew 安装完成这些链接可以删掉</span></span><br><span class="line">cd ~/miniconda3/bin/</span><br><span class="line">ln -s x86_64-conda_cos6-linux-gnu-gcc gcc</span><br><span class="line">ln -s x86_64-conda_cos6-linux-gnu-cpp c++</span><br><span class="line">ln -s gcc cc</span><br></pre></td></tr></table></figure><h2 id="配置安装环境变量"><a href="#配置安装环境变量" class="headerlink" title="配置安装环境变量"></a>配置安装环境变量</h2><p>这些环境变量也可以配置到 bashrc 等文件，使之永久生效</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置 curl 和 git，可选</span></span><br><span class="line">export HOMEBREW_CURL_PATH=~/miniconda3/bin/curl</span><br><span class="line">export HOMEBREW_GIT_PATH=~/miniconda3/bin/git</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置安装源为清华源，如果网络畅通可忽略，清华源也可能403</span></span><br><span class="line">export HOMEBREW_INSTALL_FROM_API=1</span><br><span class="line">export HOMEBREW_API_DOMAIN=&quot;https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api&quot;</span><br><span class="line">export HOMEBREW_BOTTLE_DOMAIN=&quot;https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles&quot;</span><br><span class="line">export HOMEBREW_BREW_GIT_REMOTE=&quot;https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git&quot;</span><br><span class="line">export HOMEBREW_CORE_GIT_REMOTE=&quot;https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git&quot;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="安装-HomeBrew"><a href="#安装-HomeBrew" class="headerlink" title="安装 HomeBrew"></a>安装 HomeBrew</h2><p>由于没有 root 权限，HomeBrew 需要手动安装。<br>由于是手动安装，位置与默认安装位置不同，很多预编译的包就不能用了，都得从源码编译，所以网络和机器性能以及耐心很重要。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">下载，也可使用清华源 https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git</span></span><br><span class="line">git clone https://github.com/Homebrew/brew ~/.homebrew</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装</span></span><br><span class="line">eval &quot;$(~/.homebrew/bin/brew shellenv)&quot;</span><br><span class="line">brew update --force --quiet</span><br><span class="line">chmod -R go-w &quot;$(brew --prefix)/share/zsh&quot;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">自动加载 brew</span></span><br><span class="line">echo &#x27;eval &quot;$(~/.homebrew/bin/brew shellenv)&quot;&#x27; &gt;&gt; ~/.bashrc</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://docs.brew.sh/Installation">https://docs.brew.sh/Installation</a></li><li><a href="https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/">https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/</a></li><li><a href="https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/">https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/</a></li><li><a href="https://github.com/tuna/issues/issues/1353">https://github.com/tuna/issues/issues/1353</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;一些公用的 Linux 服务器，处于维护以及安全考虑，一般只会提供普通权限用户给使用者。&lt;br&gt;普通用户的权限满足日常使用是够了，但是难以配置自己的开发环境，安装一些自己需要的包。&lt;/p&gt;
&lt;p&gt;如果都从源码编译安装软件，依赖的维护过于复杂，初始编译工具链的版本可能也不满足需求，如 gcc 版本过低。&lt;br&gt;如果申请 sudo 权限或者请求更新系统或安装 docker，后期责任难以界定，运维和管理员一般也不会同意。&lt;/p&gt;
&lt;p&gt;所以，最优方案还是有需求的用户在个人目录维护自己的工具链和环境。下文方案为围绕 HomeBrew 构建。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Linux" scheme="https://blog.ponder.work/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>给 macOS 词典增加生词本功能</title>
    <link href="https://blog.ponder.work/2022/11/13/add-wordlist-for-macOS-dictionary/"/>
    <id>https://blog.ponder.work/2022/11/13/add-wordlist-for-macOS-dictionary/</id>
    <published>2022-11-12T20:00:00.000Z</published>
    <updated>2022-11-04T19:40:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>macOS 系统的自带词典应用非常强大，与其他应用整合很好，快捷取词很方便（command+control+d）。<br>但是美中不足的是缺少生词本功能，查了单词又很容易忘记，对语言学习者来说就有些不便了。</p><p>经过本强迫症的探索，终于找到基于 Karabiner-Elements + Automator + Logseq 的完美生词本方案。<br>最后的效果是，快捷键取词的同时记录单词卡片到Logseq对应的笔记。</p><span id="more"></span><h2 id="词典词库扩充"><a href="#词典词库扩充" class="headerlink" title="词典词库扩充"></a>词典词库扩充</h2><p>参考<a href="https://zhuanlan.zhihu.com/p/433646737">知乎文章</a>安装好《朗道英汉字典5.0》<br>这是为了有个释义简洁的词典，方便后续生成生词本词条</p><p><img data-src="https://image.ponder.work/mweb/2022-11-02-16673997943438.jpg"></p><h2 id="编写workflow"><a href="#编写workflow" class="headerlink" title="编写workflow"></a>编写workflow</h2><p>使用 macOS 自带应用 Automator（自动操作）编写workflow，将当前鼠标所在位置的文本提取并保存制卡。</p><p>首先打开 Automator.app 新建一个 Quick Aciont(快速操作)<br><img data-src="https://image.ponder.work/mweb/2022-11-02-16673999123628.jpg"><br>然后依次拖入“获得词语定义”，“运行Shell脚本”等步骤，并调整如下几个位置的选项。<br><img data-src="https://image.ponder.work/mweb/2022-11-02-16674004811769.jpg"></p><p>修改脚本里的代码为如下内容，生词本路径相应替换，并相应位置新建好生词本文件。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> unicode_literals, print_function</span><br><span class="line"><span class="keyword">import</span> sys, os, io, subprocess</span><br><span class="line"></span><br><span class="line">FILE=os.path.expanduser(<span class="string">&quot;~/weiyun_sync/!sync/logseq-note/pages/生词本.md&quot;</span>)</span><br><span class="line">output = []</span><br><span class="line">text = sys.argv[<span class="number">1</span>].decode(<span class="string">&#x27;utf8&#x27;</span>) <span class="keyword">if</span> sys.version_info.major == <span class="number">2</span> <span class="keyword">else</span> sys.argv[<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line">lines = [i.strip() <span class="keyword">for</span> i <span class="keyword">in</span> text.splitlines() <span class="keyword">if</span> i.strip()]</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(lines) &lt; <span class="number">2</span>:</span><br><span class="line">    exit(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">word = lines[<span class="number">0</span>]</span><br><span class="line"><span class="keyword">if</span> lines[<span class="number">1</span>][<span class="number">0</span>] == <span class="string">&#x27;*&#x27;</span>:</span><br><span class="line">    output.append(<span class="string">&#x27;- &#123;&#125;\t&#123;&#125;  [[card]]&#x27;</span>.<span class="built_in">format</span>(word, lines[<span class="number">1</span>]))</span><br><span class="line">    lines = lines[<span class="number">2</span>:]</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    output.append(<span class="string">&#x27;- &#123;&#125;\t  [[card]]&#x27;</span>.<span class="built_in">format</span>(word))</span><br><span class="line">    lines = lines[<span class="number">1</span>:]</span><br><span class="line">output.append(<span class="string">&#x27;\t- &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(lines[<span class="number">0</span>]))</span><br><span class="line"><span class="keyword">for</span> line <span class="keyword">in</span> lines[<span class="number">1</span>:]:</span><br><span class="line">    output.append(<span class="string">&#x27;\t  &#x27;</span> + line)</span><br><span class="line"></span><br><span class="line">old_words = <span class="built_in">set</span>()</span><br><span class="line"><span class="keyword">with</span> io.<span class="built_in">open</span>(FILE, <span class="string">&#x27;r&#x27;</span>, encoding=<span class="string">&#x27;utf8&#x27;</span>) <span class="keyword">as</span> fp:</span><br><span class="line">    <span class="keyword">for</span> line <span class="keyword">in</span> fp:</span><br><span class="line">        parts = line.split()</span><br><span class="line">        <span class="keyword">if</span> line.startswith(<span class="string">&#x27;-&#x27;</span>) <span class="keyword">and</span> <span class="built_in">len</span>(parts) &gt; <span class="number">1</span>:</span><br><span class="line">            old_words.add(parts[<span class="number">1</span>])</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> word <span class="keyword">not</span> <span class="keyword">in</span> old_words:</span><br><span class="line">    <span class="keyword">with</span> io.<span class="built_in">open</span>(FILE, <span class="string">&#x27;a&#x27;</span>, encoding=<span class="string">&#x27;utf8&#x27;</span>) <span class="keyword">as</span> fp:</span><br><span class="line">        fp.write(<span class="string">&#x27;\n&#x27;</span>)</span><br><span class="line">        fp.write(<span class="string">&#x27;\n&#x27;</span>.join(output))</span><br><span class="line">        fp.write(<span class="string">&#x27;\n&#x27;</span>)</span><br><span class="line">    subprocess.check_call([<span class="string">&#x27;osascript&#x27;</span>, <span class="string">&#x27;-e&#x27;</span>, <span class="string">u&#x27;display notification &quot;添加 &#123;&#125;&quot; with title &quot;生词本&quot;&#x27;</span>.<span class="built_in">format</span>(word)])</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    subprocess.check_call([<span class="string">&#x27;osascript&#x27;</span>, <span class="string">&#x27;-e&#x27;</span>, <span class="string">u&#x27;display notification &quot;跳过 &#123;&#125;&quot; with title &quot;生词本&quot;&#x27;</span>.<span class="built_in">format</span>(word)])</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>选择路径保存好 workflow，然后在 <code>键盘 - 快捷键 - 服务</code> 中能看到新建的workflow。<br>为它设置快捷键 <code>command + shift + alt + 1</code><br><img data-src="https://image.ponder.work/mweb/2022-11-02-16674009451584.jpg"></p><h2 id="Karabiner-Elements"><a href="#Karabiner-Elements" class="headerlink" title="Karabiner-Elements"></a>Karabiner-Elements</h2><p><a href="https://karabiner-elements.pqrs.org/">Karabiner-Elements</a> 是 macOS 平台的一个重新映射快捷键的软件。<br>这里我们使用它将“查询单词”和“触发workflow”整合在一起，当然它还支持很多用途，这里就不赘述了。</p><p>注意确保Karabiner相关权限，并且设置中下图相关设备是勾选状态</p><p><img data-src="https://image.ponder.work/mweb/2022-11-04-16675619005392.jpg"></p><p>安装好Karabiner-Elements后，打开它的配置文件<br>路径在 <code>/Users/&lt;用户名&gt;/.config/karabiner/karabiner.json</code></p><p>在 <code>profiles -&gt; complex_modifications -&gt; rules</code> 列表中增加一项配置，内容如下。<br>然后保存，Karabiner会自动加载新的配置。</p><p>这里是将鼠标的侧键（靠前的）映射为查单词的快捷键，实现一键查词。<br>也可以根据需要更改按键，通过<a href="https://karabiner-elements.pqrs.org/docs/manual/operation/eventviewer/">EventViewer</a>可以查看按键代码，配置文件格式可参考<a href="https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to/">官方文档</a></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Mouse&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;manipulators&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;from&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">                <span class="attr">&quot;pointing_button&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button5&quot;</span></span><br><span class="line">            <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;to&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;pointing_button&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button1&quot;</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;pointing_button&quot;</span><span class="punctuation">:</span> <span class="string">&quot;button1&quot;</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;key_code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;d&quot;</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">&quot;modifiers&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                        <span class="string">&quot;left_command&quot;</span><span class="punctuation">,</span></span><br><span class="line">                        <span class="string">&quot;left_control&quot;</span></span><br><span class="line">                    <span class="punctuation">]</span></span><br><span class="line">                <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">                <span class="punctuation">&#123;</span></span><br><span class="line">                    <span class="attr">&quot;key_code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">                    <span class="attr">&quot;modifiers&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">                        <span class="string">&quot;left_option&quot;</span><span class="punctuation">,</span></span><br><span class="line">                        <span class="string">&quot;left_shift&quot;</span><span class="punctuation">,</span></span><br><span class="line">                        <span class="string">&quot;left_command&quot;</span></span><br><span class="line">                    <span class="punctuation">]</span></span><br><span class="line">                <span class="punctuation">&#125;</span></span><br><span class="line">            <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;basic&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><img data-src="https://image.ponder.work/mweb/2022-11-02-16674017788245.jpg"></p><h2 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h2><p>可以看到 Logseq 中卡片生成的效果<br><img data-src="https://image.ponder.work/mweb/2022-11-02-16674007610777.jpg"><br><img data-src="https://image.ponder.work/mweb/2022-11-02-16674023982597.jpg"></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://karabiner-elements.pqrs.org/docs/">https://karabiner-elements.pqrs.org/docs/</a></li><li><a href="https://zhuanlan.zhihu.com/p/433646737">https://zhuanlan.zhihu.com/p/433646737</a></li><li><a href="https://hectorguo.com/zh/save-words-in-dictionary/">https://hectorguo.com/zh/save-words-in-dictionary/</a></li><li><a href="https://github.com/jjgod/mac-dictionary-kit">https://github.com/jjgod/mac-dictionary-kit</a></li><li><a href="https://lightcss.com/mac-dictionary/">https://lightcss.com/mac-dictionary/</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;macOS 系统的自带词典应用非常强大，与其他应用整合很好，快捷取词很方便（command+control+d）。&lt;br&gt;但是美中不足的是缺少生词本功能，查了单词又很容易忘记，对语言学习者来说就有些不便了。&lt;/p&gt;
&lt;p&gt;经过本强迫症的探索，终于找到基于 Karabiner-Elements + Automator + Logseq 的完美生词本方案。&lt;br&gt;最后的效果是，快捷键取词的同时记录单词卡片到Logseq对应的笔记。&lt;/p&gt;</summary>
    
    
    
    <category term="工作生活" scheme="https://blog.ponder.work/categories/life/"/>
    
    
    <category term="mac" scheme="https://blog.ponder.work/tags/mac/"/>
    
  </entry>
  
  <entry>
    <title>关闭子进程打开的文件描述符</title>
    <link href="https://blog.ponder.work/2022/08/31/close-subprocess-opened-fd/"/>
    <id>https://blog.ponder.work/2022/08/31/close-subprocess-opened-fd/</id>
    <published>2022-08-30T22:48:00.000Z</published>
    <updated>2022-08-31T15:32:47.000Z</updated>
    
    <content type="html"><![CDATA[<p>我们在测试代码时，由于需要经常重启服务，经常会发现服务端口被占用。<br>一般kill掉后台进程就ok了，但是如果服务有启动一些常驻的后台程序，可能也会导致端口不能释放。</p><p>在类UNIX系统中，一切被打开的文件、端口被抽象为文件描述符（file descriptor）<br>从python3.4开始，文件描述符默认是non-inheritable，也就是子进程不会共享文件描述符。</p><span id="more"></span><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>一般为了实现多进程、多线程的webserver，服务端口fd必须设置为继承（set_inheritable），这样才能多进程监听一个端口（配合SO_REUSEPORT）<br>典型的是使用flask的测试服务器的场景，这里我们写一段代码模拟。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket, os</span><br><span class="line">server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line">server.bind((<span class="string">&#x27;127.0.0.1&#x27;</span>, <span class="number">22222</span>))</span><br><span class="line">server.set_inheritable(<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line">os.system(<span class="string">&quot;python -c &#x27;import time;time.sleep(1000)&#x27; &quot;</span>)</span><br></pre></td></tr></table></figure><p>我们通过<code>lsof -p {pid}</code>可以看到这两个进程的所有文件描述符<br>server进程, 可以看到服务端口的fd是4</p><figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">COMMAND   PID  FD      TYPE             DEVICE  SIZE/OFF       NODE NAME</span><br><span class="line">ptpython<span class="number"> 6214 </span>cwd       DIR              253,0     <span class="number"> 4096 </span><span class="number"> 872946898 </span>/</span><br><span class="line">...</span><br><span class="line">ptpython<span class="number"> 6214 </span>  0u      CHR             136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">ptpython<span class="number"> 6214 </span>  1u      CHR             136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">ptpython<span class="number"> 6214 </span>  2u      CHR             136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">ptpython<span class="number"> 6214 </span>  3r      CHR                1,9       0t0      <span class="number"> 2057 </span>/dev/urandom</span><br><span class="line">ptpython<span class="number"> 6214 </span>  4u     sock                0,7       0t0  <span class="number"> 58345077 </span>protocol: TCP</span><br><span class="line">ptpython<span class="number"> 6214 </span>  5u  a_inode               0,10        <span class="number"> 0 </span>     <span class="number"> 8627 </span>[eventpoll]</span><br><span class="line">ptpython<span class="number"> 6214 </span>  6u     unix 0x0000000000000000       0t0  <span class="number"> 58368029 </span>socket</span><br><span class="line">ptpython<span class="number"> 6214 </span>  7u     unix 0x0000000000000000       0t0  <span class="number"> 58368030 </span>socket</span><br></pre></td></tr></table></figure><p>sleep子进程，也拥有fd&#x3D;4的文件描述符</p><figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">COMMAND   PID  FD   TYPE DEVICE  SIZE/OFF       NODE NAME</span><br><span class="line">python <span class="number"> 18022 </span>cwd    DIR  253,0     <span class="number"> 4096 </span><span class="number"> 872946898 </span>/</span><br><span class="line">...</span><br><span class="line">python <span class="number"> 18022 </span>  0u   CHR 136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">python <span class="number"> 18022 </span>  1u   CHR 136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">python <span class="number"> 18022 </span>  2u   CHR 136,13       0t0        <span class="number"> 16 </span>/dev/pts/13</span><br><span class="line">python <span class="number"> 18022 </span>  4u  sock    0,7       0t0  <span class="number"> 58345077 </span>protocol: TCP</span><br></pre></td></tr></table></figure><p>如果server进程退出时，sleep进程没有退出，fd&#x3D;4对应的端口就被占用了，服务也就不能正常启动了。</p><h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><h3 id="手动清理"><a href="#手动清理" class="headerlink" title="手动清理"></a>手动清理</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">os.system(<span class="string">f&#x27;lsof -p <span class="subst">&#123;os.getpid()&#125;</span>&#x27;</span>)</span><br><span class="line">os.closerange(<span class="number">3</span>, <span class="number">100</span>)  <span class="comment"># 这里假定打开文件描述符不会超过100</span></span><br><span class="line">time.sleep(<span class="number">5</span>)</span><br><span class="line">os.system(<span class="string">f&#x27;lsof -p <span class="subst">&#123;os.getpid()&#125;</span>&#x27;</span>)</span><br><span class="line"><span class="comment"># 后面执行需要的业务代码</span></span><br></pre></td></tr></table></figure><h3 id="使用close-fds"><a href="#使用close-fds" class="headerlink" title="使用close_fds"></a>使用close_fds</h3><p>使用subprocess库而不是os来启动子程序， 通过close_fds参数关闭多余的文件描述符</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> subprocess</span><br><span class="line">subprocess.call(<span class="string">&quot;python -c &#x27;import time;time.sleep(1000)&#x27;&quot;</span>, shell=<span class="literal">True</span>, close_fds=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://docs.python.org/3/library/os.html#inheritance-of-file-descriptors">https://docs.python.org/3/library/os.html#inheritance-of-file-descriptors</a></li><li><a href="https://docs.python.org/3/library/subprocess.html#subprocess.Popen">https://docs.python.org/3/library/subprocess.html#subprocess.Popen</a></li><li><a href="https://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python#answer-25069136">https://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python#answer-25069136</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;我们在测试代码时，由于需要经常重启服务，经常会发现服务端口被占用。&lt;br&gt;一般kill掉后台进程就ok了，但是如果服务有启动一些常驻的后台程序，可能也会导致端口不能释放。&lt;/p&gt;
&lt;p&gt;在类UNIX系统中，一切被打开的文件、端口被抽象为文件描述符（file descriptor）&lt;br&gt;从python3.4开始，文件描述符默认是non-inheritable，也就是子进程不会共享文件描述符。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Python" scheme="https://blog.ponder.work/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>容器内进程优雅退出</title>
    <link href="https://blog.ponder.work/2022/07/11/gracefully-shutdown-container/"/>
    <id>https://blog.ponder.work/2022/07/11/gracefully-shutdown-container/</id>
    <published>2022-07-10T21:48:00.000Z</published>
    <updated>2022-07-11T15:34:10.000Z</updated>
    
    <content type="html"><![CDATA[<p>在使用 docker 时，常常会碰到进程退出时资源清理的问题，比如保证当前请求处理完成，再退出程序。</p><p>当执行 <code>docker stop xxx</code> 时，docker会向主进程（pid&#x3D;1）发送 <code>SIGTERM</code> 信号<br>如果在一定时间(默认为10s)内进程没有退出，会进一步发送 <code>SIGKILL</code> 直接杀死程序，该信号既不能被捕捉也不能被忽略。</p><p>一般的web框架或者rpc框架都集成了 <code>SIGTERM</code> 信号处理程序， 一般不用担心优雅退出的问题。<br>但是如果你的容器内有多个程序（称为胖容器，一般不推荐），那么就需要做一些操作保证所有程序优雅退出。</p><span id="more"></span><h2 id="signals"><a href="#signals" class="headerlink" title="signals"></a>signals</h2><p>信号是一种进程间通信机制，它给应用程序提供一种异步的软件中断，使应用程序有机会接受其他程序活终端发送的命令(即信号)。</p><p>应用程序收到信号后，有三种处理方式：忽略，默认，或捕捉。</p><p>常见信号：</p><table><thead><tr><th>信号名称</th><th>信号数</th><th>描述</th><th align="left">默认操作</th></tr></thead><tbody><tr><td>SIGHUP</td><td>1</td><td>当用户退出Linux登录时，前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。对于与终端脱离关系的守护进程，这个信号用于通知它重新读取配置文件。</td><td align="left">终止进程</td></tr><tr><td>SIGINT</td><td>2</td><td>程序终止(interrupt)信号，在用户键入 Ctrl+C 时发出。</td><td align="left">终止进程</td></tr><tr><td>SIGQUIT</td><td>3</td><td>和SIGINT类似，但由QUIT字符(通常是Ctrl &#x2F;)来控制。</td><td align="left">终止进程并dump core</td></tr><tr><td>SIGFPE</td><td>8</td><td>在发生致命的算术运算错误时发出。不仅包括浮点运算错误，还包括溢出及除数为0等其它所有的算术错误。</td><td align="left">终止进程并dump core</td></tr><tr><td>SIGKILL</td><td>9</td><td>用来立即结束程序的运行。本信号不能被阻塞，处理和忽略。</td><td align="left">终止进程</td></tr><tr><td>SIGALRM</td><td>14</td><td>时钟定时信号，计算的是实际的时间或时钟时间。alarm 函数使用该信号。</td><td align="left">终止进程</td></tr><tr><td>SIGTERM</td><td>15</td><td>通常用来要求程序自己正常退出；kill 命令缺省产生这个信号。</td><td align="left">终止进程</td></tr></tbody></table><h2 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h2><p>下面以 supervisor 为例，Dockerfile 如下</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> centos:centos7</span><br><span class="line"><span class="keyword">ENV</span> PYTHONUNBUFFERED=<span class="number">1</span> TZ=Asia/Shanghai</span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> yum -y install epel-release &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">    yum -y install supervisor &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">    yum -y clean all  &amp;&amp; <span class="built_in">rm</span> -rf /var/cache</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> ./ /root/</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [ <span class="string">&quot;/usr/bin/supervisord&quot;</span>, <span class="string">&quot;-n&quot;</span>, <span class="string">&quot;-c&quot;</span>, <span class="string">&quot;/etc/supervisord.conf&quot;</span> ]</span></span><br></pre></td></tr></table></figure><h2 id="trap"><a href="#trap" class="headerlink" title="trap"></a>trap</h2><p>正常情况，容器退出时supervisor启动的其他程序并不会收到 <code>SIGTERM</code> 信号，导致子程序直接退出了。</p><p>这里使用 <code>trap</code> 对程序的异常处理进行包装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">trap</span> &lt;siginal handler&gt; &lt;signal 1&gt; &lt;signal 2&gt; ...</span><br></pre></td></tr></table></figure><p>新建一个初始化脚本，<code>init.sh</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/sh</span></span><br><span class="line"></span><br><span class="line">/usr/bin/supervisord -n -c /etc/supervisord.conf &amp;</span><br><span class="line"></span><br><span class="line"><span class="built_in">trap</span> <span class="string">&quot;supervisorctl stop all &amp;&amp; sleep 3&quot;</span> TERM INT</span><br><span class="line"></span><br><span class="line"><span class="built_in">wait</span></span><br></pre></td></tr></table></figure><p>修改 ENTRYPOINT 为如下</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ENTRYPOINT <span class="selector-attr">[<span class="string">&quot;sh&quot;</span>, <span class="string">&quot;/root/init.sh&quot;</span>]</span></span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.ctl.io/developers/blog/post/gracefully-stopping-docker-containers/">https://www.ctl.io/developers/blog/post/gracefully-stopping-docker-containers/</a></li><li><a href="https://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html">https://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html</a></li><li><a href="https://wangchujiang.com/linux-command/c/trap.html">https://wangchujiang.com/linux-command/c/trap.html</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;在使用 docker 时，常常会碰到进程退出时资源清理的问题，比如保证当前请求处理完成，再退出程序。&lt;/p&gt;
&lt;p&gt;当执行 &lt;code&gt;docker stop xxx&lt;/code&gt; 时，docker会向主进程（pid&amp;#x3D;1）发送 &lt;code&gt;SIGTERM&lt;/code&gt; 信号&lt;br&gt;如果在一定时间(默认为10s)内进程没有退出，会进一步发送 &lt;code&gt;SIGKILL&lt;/code&gt; 直接杀死程序，该信号既不能被捕捉也不能被忽略。&lt;/p&gt;
&lt;p&gt;一般的web框架或者rpc框架都集成了 &lt;code&gt;SIGTERM&lt;/code&gt; 信号处理程序， 一般不用担心优雅退出的问题。&lt;br&gt;但是如果你的容器内有多个程序（称为胖容器，一般不推荐），那么就需要做一些操作保证所有程序优雅退出。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Linux" scheme="https://blog.ponder.work/tags/Linux/"/>
    
    <category term="Docker" scheme="https://blog.ponder.work/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>Python 循环变量泄露与延迟绑定</title>
    <link href="https://blog.ponder.work/2022/03/05/python-loop-variables-leak/"/>
    <id>https://blog.ponder.work/2022/03/05/python-loop-variables-leak/</id>
    <published>2022-03-04T20:17:00.000Z</published>
    <updated>2022-03-05T13:15:04.000Z</updated>
    
    <content type="html"><![CDATA[<p>循环变量泄露与延迟绑定叠加在一起，会产生一些让人迷惑的结果。</p><span id="more"></span><h2 id="梦开始的地方"><a href="#梦开始的地方" class="headerlink" title="梦开始的地方"></a>梦开始的地方</h2><p>先看看一开始的问题，可以看到这里lambda函数的返回值一直在变。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">xx = []</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]:</span><br><span class="line">    xx.append(<span class="keyword">lambda</span>: i)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;a:&#x27;</span>, xx[<span class="number">0</span>]())</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> j <span class="keyword">in</span> xx:</span><br><span class="line">    <span class="built_in">print</span>(j())</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;b:&#x27;</span>, xx[<span class="number">0</span>]())</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> xx:</span><br><span class="line">    <span class="built_in">print</span>(i, i())</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;c:&#x27;</span>, xx[<span class="number">0</span>], xx[<span class="number">0</span>]())</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>]:</span><br><span class="line">    <span class="built_in">print</span>(i)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;d:&#x27;</span>, xx[<span class="number">0</span>], xx[<span class="number">0</span>]())</span><br></pre></td></tr></table></figure><p>输出如下</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="params">a:</span> <span class="number">3</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="params">b:</span> <span class="number">3</span></span><br><span class="line"><span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30310<span class="operator">&gt;</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30310<span class="operator">&gt;</span></span><br><span class="line"><span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca303a0<span class="operator">&gt;</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca303a0<span class="operator">&gt;</span></span><br><span class="line"><span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30430<span class="operator">&gt;</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30430<span class="operator">&gt;</span></span><br><span class="line"><span class="params">c:</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30310<span class="operator">&gt;</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30430<span class="operator">&gt;</span></span><br><span class="line"><span class="number">4</span></span><br><span class="line"><span class="number">5</span></span><br><span class="line"><span class="number">6</span></span><br><span class="line"><span class="params">d:</span> <span class="operator">&lt;</span>function main2.<span class="symbol">&lt;locals&gt;</span>.<span class="symbol">&lt;lambda&gt;</span> at <span class="number">0</span>x10ca30310<span class="operator">&gt;</span> <span class="number">6</span></span><br></pre></td></tr></table></figure><h2 id="循环变量泄露"><a href="#循环变量泄露" class="headerlink" title="循环变量泄露"></a>循环变量泄露</h2><p>由于Python没有块级作用域，所以循环会改变当前作用域变量的值，也就是循环变量泄露。<br><strong>注意</strong>：Python3中列表推导式循环变量不会泄露，Python2中和常规循环一样泄露。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">x = -<span class="number">1</span></span><br><span class="line"><span class="keyword">for</span> x <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">7</span>):</span><br><span class="line">    <span class="keyword">if</span> x == <span class="number">6</span>:</span><br><span class="line">        <span class="built_in">print</span>(x, <span class="string">&#x27;: for x inside loop&#x27;</span>)</span><br><span class="line"><span class="built_in">print</span>(x, <span class="string">&#x27;: x in global&#x27;</span>)</span><br></pre></td></tr></table></figure><p>输出如下</p><figure class="highlight basic"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">6 </span>: <span class="keyword">for</span> x inside loop</span><br><span class="line"><span class="symbol">6 </span>: x in global</span><br></pre></td></tr></table></figure><h2 id="闭包与延迟绑定"><a href="#闭包与延迟绑定" class="headerlink" title="闭包与延迟绑定"></a>闭包与延迟绑定</h2><p>再讲一下<strong>闭包</strong>，在一个内部函数中，对外部作用域的变量进行引用，(并且一般外部函数的返回值为内部函数)，那么内部函数就被认为是闭包。<br>这里所谓的引用可以也就是内部函数记住了<strong>变量的名称</strong>（而不是值，这个从ast语法树可以看出），而变量对应的值是会变化的。<br>如果在循环中定义闭包，引用的变量的值在循环结束才统一确定为最后一次循环时的值，也就是<strong>延迟绑定</strong>（lazy binding）。</p><p>所以下面的例子，<code>xx</code>的所有匿名函数的返回值均为<code>3</code></p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">xx = <span class="selector-attr">[]</span></span><br><span class="line"><span class="keyword">for</span> <span class="selector-tag">i</span> <span class="keyword">in</span> <span class="selector-attr">[1,2,3]</span>:</span><br><span class="line">    xx<span class="selector-class">.append</span>(lambda: i)</span><br></pre></td></tr></table></figure><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>再分析一开始的问题，这里的匿名函数引用了变量<code>i</code>，而<code>i</code>是全局变量，所以再次使用<code>i</code>作为循环变量时，列表中的匿名函数引用的值就被覆盖了。</p><p>正确做法：</p><ul><li>在独立的函数中定义闭包</li><li>闭包引用的变量应该是其他函数不可修改的</li><li>优先使用列表推导式</li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://stackoverflow.com/questions/3611760/scoping-in-python-for-loops">https://stackoverflow.com/questions/3611760/scoping-in-python-for-loops</a></li><li><a href="https://www.educative.io/courses/python-ftw-under-the-hood/N8RW8508RkL">https://www.educative.io/courses/python-ftw-under-the-hood/N8RW8508RkL</a></li><li><a href="https://mail.python.org/pipermail/python-ideas/2008-October/002109.html">https://mail.python.org/pipermail/python-ideas/2008-October/002109.html</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;循环变量泄露与延迟绑定叠加在一起，会产生一些让人迷惑的结果。&lt;/p&gt;</summary>
    
    
    
    <category term="编程" scheme="https://blog.ponder.work/categories/programming/"/>
    
    
    <category term="Python" scheme="https://blog.ponder.work/tags/Python/"/>
    
  </entry>
  
</feed>
