-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
475 lines (259 loc) · 253 KB
/
Copy pathatom.xml
File metadata and controls
475 lines (259 loc) · 253 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
<?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-09-19T15:43:00.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>真正的手艺活</title>
<link href="https://blog.ponder.work/2026/09/19/my-handicrafts/"/>
<id>https://blog.ponder.work/2026/09/19/my-handicrafts/</id>
<published>2026-09-19T15:43:00.000Z</published>
<updated>2026-09-19T15:43:00.000Z</updated>
<content type="html">< --></p><p><img data-src="https://image.ponder.work/mweb/2026-09-20---IMG_20210823_131755.jpg" alt="alt text"> <!-- original:  --></p><p><video controls src="/media/my-handicrafts/wx_camera_1585309305271.mp4" title="Title"></video></p><p><img data-src="https://image.ponder.work/mweb/2026-09-20---IMG_20200213_231429_1789833290259edit.jpg" alt="alt text"> <!-- original:  --></p><p><img data-src="https://image.ponder.work/mweb/2026-09-20---IMG_20200116_220619.jpg" alt="alt text"> <!-- original:  --></p><p><img data-src="https://image.ponder.work/mweb/2026-09-20---IMG_20160702_223235.jpg" alt="alt text"> <!-- original:  --></p><p><img data-src="https://image.ponder.work/mweb/2026-09-20---IMG_20160702_222459.jpg" alt="alt text"> <!-- original:  --></p><p><img data-src="https://image.ponder.work/mweb/2026-09-20---IMG_20160624_202410.jpg" alt="alt text"> <!-- original:  --></p><p><img data-src="https://image.ponder.work/mweb/2026-09-20---IMG_20160617_221903_1466173159774.jpg" alt="alt text"> <!-- original:  --></p><p><img data-src="https://image.ponder.work/mweb/2026-09-20---IMG_20160617_221856_1466173339879.jpg" alt="alt text"> <!-- original:  --></p>]]></content>
<summary type="html"><p>记录做过的手工</p></summary>
<category term="工作生活" scheme="https://blog.ponder.work/categories/life/"/>
<category term="手工, 折纸" scheme="https://blog.ponder.work/tags/%E6%89%8B%E5%B7%A5%EF%BC%8C-%E6%8A%98%E7%BA%B8/"/>
</entry>
<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/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/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/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. 网络 -> 接口 -> 设备 -> br-lan 网桥 -> 配置</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. 网络 -> 接口 -> lan -> 编辑</p><ol><li>将设备改为 “软件 VLAN: br-lan.1”</li><li>保存不要应用</li></ol><p>C. 网络 -> 接口 -> wan -> 编辑</p><ol><li>将设备改为 “软件 VLAN: br-lan.10”</li><li>保存不要应用</li></ol><p>D. 网络 -> 接口 -> wan6 -> 编辑</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&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"><p>因为房子的大小布局不同,个人的要求不同,家庭网络的设备布局一直没有完美的方案。<br>而且很多人的网络知识不够,导致最终家庭网络称不上好用。如果有一定的网络知识,vlan是简化家庭网络的利器。</p></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/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 '[1,1]' -t 30</code></p><blockquote><p>model_68_64_65.onnx 是一个小型决策树模型,用于测试 openvino 功能</p></blockquote><p>然而,出现以下错误,<code>'work_group_reduce_max' 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">"/opt/intel/openvino/python/openvino/tools/benchmark/main.py"</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">"/opt/intel/openvino/python/openvino/_ov_api.py"</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, {} <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">'false'</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">'work_group_reduce_max'</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">'sub_group_reduce_max'</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">'work_group_reduce_add'</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">'work_group_reduce_max'</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 "OpenCL C"</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 / Gen9(UHD 500/600 / Iris 540/550)1.2(仅部分 2.0 功能)</li><li>Intel Gen11(UHD620 / Whiskey Lake 之后)1.2 / 部分 2.0(仍不完整)</li><li>Intel Gen12(Iris Xe / 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"><p>日常生活中有碰到轻量模型的推理需求,比如验证码识别、tts生成等。<br>想要这个服务24h可用,就不能在日常电脑上部署。正好有个 J4125 的迷你主机作为家庭服务器,就先把模型部署到上面,验证下是否满足需求。</p></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">< --></p><h2 id="迭代回顾"><a href="#迭代回顾" class="headerlink" title="迭代回顾"></a>迭代回顾</h2><p>以下是 Claude Sonnet/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 叠加区域框,支持显示/隐藏。</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=0 时自动计算为图像面积 0.5%。</p></li><li><p><strong>算法 v3:投影法</strong> — 水平投影行分割 → 行内垂直投影列分割。纯白图像 Otsu 退化通过 gray.std() < 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/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/50/120px)构建层级树。聚合块不符合 UI 布局逻辑,效果差。</p></li><li><p><strong>OCR + 行列聚类</strong> — 先按 Y 聚行(重叠或间距<5px),行内按 X 聚组(间距<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=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><g></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 / 直方图均衡化 / 线性拉伸三种方法。结论:该图 90% 像素在 224-255 区间,对比度不是瓶颈,三种方法检测到的线完全一致。</p></li><li><p><strong>对比度增强调研(image.png)</strong> — 同样方法分析 image.png。发现 CLAHE 多检测到 1 条线 y=42(标题栏/菜单栏分隔线,灰度 240 vs 255,梯度仅 15)。原始 Canny 在该行覆盖率为 0,CLAHE 将微弱梯度放大后相邻行达到 97-100% 覆盖率。主内容区(mean=254.9, std=3.4)无改善——几乎纯白,无结构线可增强。</p></li><li><p><strong>加入 CLAHE 预处理</strong> — 在 Canny 之前加 <code>CLAHE(clipLimit=2.0, tileGridSize=(8,8))</code>。image.png 从 33 个区域增至 34 个,新增 y=42 分界线。screen.png 从原有区域增至 71 个,菜单栏内部被进一步切割。代价极小(一次 CLAHE 运算),无误判。</p></li><li><p><strong>自适应 ratio 诊断(image2.png)</strong> — 区域 32(x=501, y=130, w=1419, h=55)内部应有横线但未被切割。诊断发现 y=132 和 y=158 两条真实分界线(灰度 231 横线)在子区域内覆盖率 87%,差 90% 阈值 3 个百分点。缺口原因:线在局部被 UI 元素打断(按钮/标签背景色与线颜色相同),不是对比度问题。</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=131, h=27)和工具栏行(y=158, h=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=174 在全宽 1920px 下覆盖率 92%,通过 ratio 阈值。但右半部分 99.8%、左半部分仅 69.4%(文字碎片拼凑,非真实线)。加入最大间隙检查:<code>max_gap_allowed = int(length * 0.15)</code>,检查前导间隙、尾部间隙和最大内部间隙(<code>np.diff(np.where(line > 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=152 和 x=154),group 中心 x=153 本身没有边缘像素,覆盖率接近 0。<code>_axis_avg_coverage</code> 基于这些错误位置计算,导致轴选择比较失真。修复:group 代表位置改为 group 内覆盖率最高的位置。</p></li><li><p><strong>单刀切割 + 大块优先</strong> — 用户指出核心问题:同方向全切产生的窄条带中,垂直于切割方向的线覆盖率不足。例如全图横切产生 y=63..176 条带(113px 高),x=152 在其中覆盖率仅 70%(因为标签栏区域打断了竖线),无法被检测。但如果先纵切(x=500),在 1920x1012 的大区域中 x=152 覆盖率 91%,能通过阈值。</p><p>改为单刀策略:每次从横纵所有候选线中选覆盖率最高的一条切一刀,分成两块,大块先递归。这样子区域尽可能大,更多的线能在大区域中被检测到。<code>max_depth</code> 从 6 提高到 20 适配二叉树更深的层级。</p><p>效果:image6.png 中 y=96(”基金总持仓”标签下方分隔线)在 x=0..152 列内覆盖率 100%,成功被检测到。</p></li><li><p><strong>ratio 简化为全局 0.85</strong> — (用户提出)长度自适应 ratio(≤100px→0.95, ≥1000px→0.85)在单刀策略下不再必要。旧策略中短线需要严格阈值防止文字碎片误判,但单刀策略每次只选最强线,加上 15% 间隙检查已经充分过滤假线。统一使用 ratio=0.85,消除了窄区域(如 29px 高的 toolbar 行)中竖线因阈值过严(0.95 要求 28/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 > 0)</code> 逐行循环调用 126911 次(254ms)。瓶颈清晰:Python 层面的逐行循环。</p></li><li><p><strong>向量化重写</strong> — 将逐行 <code>np.sum(line > 0)</code> 替换为 <code>np.count_nonzero(edges_roi, axis=1/0)</code> 一次性计算所有行/列的边缘像素数。返回类型从 <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 > 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"> 最大间隙检查:任何连续空白 > 线长 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 > 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>算法设计师 / 研究型 PM / 强技术判断者</strong>。</p><p><strong>1. 用户是问题建模者</strong><br>最关键的转折——“像切蛋糕一样找贯通线切割”——直接改变了整个解法空间。文中把这一步称为“整个过程最关键的一步”,因为它不是换算法,而是换了问题表述:从“检测区域”变成“寻找能把区域分开的贯通线”。这是典型的一阶抽象能力。</p><p><strong>2. 用户是方向校正者</strong><br>后面另一个关键点,“切开之后选面积更大的那边先继续切”,再次不是参数建议,而是对递归策略本质的纠偏。文中甚至直接说,这是从“批量切割”到“逐步切割”的范式转换,比调参数重要得多。说明用户能够看穿失败表象,定位真正导致漏检的机制。</p><p><strong>3. 用户是评价标准提供者</strong><br>很多地方能看出,用户不仅看结果对不对,还知道为什么不对。比如指出深度递减 ratio 的方向错误,认为“深度不是好的代理变量”;再比如提出对比度增强的思路,推动诊断 screen.png 和 image.png。说明用户脑中有比较清晰的误判/漏判机理模型,而不是只凭直觉说“效果不好”。</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. 算法/图像/视觉方向工程师"></a>1. 算法/图像/视觉方向工程师</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. 偏研究型的软件工程师 / 技术负责人"></a>2. 偏研究型的软件工程师 / 技术负责人</h4><p>文中反复出现的不是“功能有没有上线”,而是:</p><ul><li>本质问题是什么</li><li>这个代理变量为什么不成立</li><li>这个框架为什么会系统性失败</li><li>如何把经验写成方法论</li></ul><p>这很像一个会亲自下场,但思维层级又高于纯实现的工程师,可能是 senior / staff 往上的类型。</p><h4 id="3-也可能是技术型产品或创业者"><a href="#3-也可能是技术型产品或创业者" class="headerlink" title="3. 也可能是技术型产品或创业者"></a>3. 也可能是技术型产品或创业者</h4><p>如果不是纯工程岗位,也很可能是<strong>非常强技术判断力的产品/创业型角色</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>用户大概率具备较强工程/算法背景,抽象能力强,偏第一性原理,善于用 AI 做高速实验,并且有把经验上升为方法论的习惯。</p><h2 id="一点感受"><a href="#一点感受" class="headerlink" title="一点感受"></a>一点感受</h2><p>SOTA 模型确实很强,明确的已有成熟方案的问题基本一次通过。<br>但当前人的作用还是不可轻易忽略,复杂问题需要关注模型的方案,及时中断并纠正(不一定要准确,提供可能的方向也行),防止其跑偏。</p>]]></content>
<summary type="html"><p>文本是用 Claude Sonnet&#x2F;Opus 4.6 开发 “UI 区域检测”功能的过程记录,文章大部分内容为 llm 生成。</p>
<p>因为要做些 ui 自动化的功能,涉及到区域检测。这个功能大概用了 5h+,本文主要记录整个迭代演进过程,以及 llm 对这个过程的评价。</p></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">"import certifi; print(certifi.where())"</span>) \</span><br><span class="line">~<span class="regexp">/.mitmproxy/mi</span>tmproxy-ca-cert.pem > ~<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">"JSON (中文)"</span></span><br><span class="line"> syntax_highlight = <span class="string">"yaml"</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>) -> <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>) -> <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">"json"</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"><p>做 LLM Agent 开发时,经常要查看模型的请求与响应,需要一个便捷的工具。<br>mitmproxy 是一个功能强大的开源中间人代理,通常用于网络调试、分析 HTTP 和 HTTPS 流量。</p></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">'clear'</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"><p>国行手机的 esim 功能有较多限制,使用不太方便。<br>但我们可以使用 esim 适配器,将 esim 转为实体 sim 卡。</p></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, 其他去广告插件也类似, 进入插件设置 -> 自定义静态规则,添加如下规则。<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">'/video/'</span>))</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>B 站非常逆天,只要你使用网页端看视频,点开首页推荐的视频,观看一定时长,就有概率会触发自动关注该 UP 主。<br>之前也有怀疑是某个浏览器插件的锅,但更换浏览器依然能够触发这个问题。</p>
<blockquote>
<p>后来发现疑似是 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/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">"pnpm"</span>: {</span><br><span class="line"> <span class="string">"onlyBuiltDependencies"</span>: [</span><br><span class="line"> <span class="string">"hexo-word-counter"</span>,</span><br><span class="line"> <span class="string">"hexo-util"</span></span><br><span class="line"> ]</span><br><span class="line"> }</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/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> {{ <span class="string">title</span> }}</span><br><span class="line"><span class="attr">date:</span> {{ <span class="string">date</span> }}</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> {{ <span class="string">title</span> }}</span><br><span class="line"><span class="attr">date:</span> {{ <span class="string">date</span> }}</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/_data/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> {<span class="attribute">counter-reset</span>: h1}</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="attribute">counter-reset</span>: h2}</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="attribute">counter-reset</span>: h3}</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="attribute">counter-reset</span>: h4}</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="attribute">counter-reset</span>: h5}</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="attribute">counter-reset</span>: h6}</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> {</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">". "</span></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">h3</span><span class="selector-pseudo">:before</span> {</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">"."</span> <span class="built_in">counter</span>(h2) <span class="string">" "</span></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">h4</span><span class="selector-pseudo">:before</span> {</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">"."</span> <span class="built_in">counter</span>(h2) <span class="string">"."</span> <span class="built_in">counter</span>(h3) <span class="string">" "</span></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">h5</span><span class="selector-pseudo">:before</span> {</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">"."</span> <span class="built_in">counter</span>(h2) <span class="string">"."</span> <span class="built_in">counter</span>(h3) <span class="string">"."</span> <span class="built_in">counter</span>(h4) <span class="string">" "</span></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">h6</span><span class="selector-pseudo">:before</span> {</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">"."</span> <span class="built_in">counter</span>(h2) <span class="string">"."</span> <span class="built_in">counter</span>(h3) <span class="string">"."</span> <span class="built_in">counter</span>(h4) <span class="string">"."</span> <span class="built_in">counter</span>(h5) <span class="string">" "</span></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">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> {</span><br><span class="line"> <span class="attribute">content</span>: <span class="string">""</span>;</span><br><span class="line"> <span class="attribute">counter-increment</span>: none</span><br><span class="line">}</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> {</span><br><span class="line"> <span class="attribute">display</span>: none</span><br><span class="line">}</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/_data/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"><script></span><br><span class="line"> <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">'pjax:success'</span>, <span class="function">() =></span> {</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">'remove'</span>) {</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">'.post-toc:not(.placeholder-toc)'</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 && <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">'sidebar-active'</span>)) {</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">'sidebar:hide'</span>));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></script></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/_data/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">{% <span class="keyword">if</span> page.<span class="property">title</span> === <span class="string">'404 Page Not Found'</span> or page.<span class="property">type</span> === <span class="string">'404'</span> or page.<span class="property">layout</span> === <span class="string">'404'</span> %}</span><br><span class="line"><script></span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="comment">// 从 Hexo 配置中获取 category_map</span></span><br><span class="line"> <span class="keyword">var</span> categoryMap = {{ config.<span class="property">category_map</span> | dump | safe }};</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) {</span><br><span class="line"> <span class="keyword">if</span> (currentPath.<span class="title function_">includes</span>(key)) {</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) {</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"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"></script></span><br><span class="line">{% endif %}</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/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">{</span></span><br><span class="line"> <span class="attr">"origins"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="string">"https://blog.ponder.work"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"https://ruanimal.github.io"</span></span><br><span class="line"> <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure><p>如果你和我一样,博客有多个域名,副域名登陆跳转会失效,可以通过注入js修正<br>utterances 的登陆跳转是根据 canonical link,我们只要让它和当前url保持一致就行了 </p><p>在 source/_data/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"><script></span><br><span class="line">(<span class="keyword">function</span>(<span class="params"></span>) {</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">'head > link[rel="canonical"]'</span>);</span><br><span class="line"> <span class="keyword">if</span> (canonical && canonical.<span class="property">href</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</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"> } <span class="keyword">catch</span> (e) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">'Failed to update canonical link:'</span>, e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})();</span><br><span class="line"></script></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"><p>昨天花了些时间将 Hexo + next 的博客版本更新了下,发现不少问题。<br>博客已经很多年了,一直用的是最开始的Hexo版本,nodejs 也是 v12,next 主题也是早年源码安装的,还在上面做了修改。<br>也是早就明白需要更新了,但是觉得麻烦,就一直没动,最后变得相当麻烦。</p></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">" [External]</span></span><br><span class="line"><span class="string">Display (KOS2718): 3840x2160 @ 60 Hz (as 1920x1080) in 27"</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 -> 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'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">"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 && echo <span class="string">"<span class="variable">${UBUNTU_CODENAME:-<span class="variable">$VERSION_CODENAME</span>}</span>"</span>)</span> stable"</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 > /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>::<后缀>/::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 <NETWORK NAME> 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> <<<span class="string">EOF > /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>微信/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"><<span class="name">disk</span> <span class="attr">type</span>=<span class="string">'block'</span> <span class="attr">device</span>=<span class="string">'disk'</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">driver</span> <span class="attr">name</span>=<span class="string">'qemu'</span> <span class="attr">type</span>=<span class="string">'raw'</span> <span class="attr">cache</span>=<span class="string">'none'</span> <span class="attr">io</span>=<span class="string">'native'</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">source</span> <span class="attr">dev</span>=<span class="string">'/dev/disk/by-id/ata-TOSHIBA'</span> <span class="attr">index</span>=<span class="string">'2'</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">target</span> <span class="attr">dev</span>=<span class="string">'sdg'</span> <span class="attr">bus</span>=<span class="string">'scsi'</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">disk</span>></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"><disk type<span class="operator">=</span><span class="string">"block"</span> device<span class="operator">=</span><span class="string">"disk"</span>></span><br><span class="line"> <driver name<span class="operator">=</span><span class="string">"qemu"</span> type<span class="operator">=</span><span class="string">"raw"</span> cache<span class="operator">=</span><span class="string">"none"</span>/></span><br><span class="line"> <source dev<span class="operator">=</span><span class="string">"/dev/disk/by-id/ata-ST3000DM008"</span>/></span><br><span class="line"> <target dev<span class="operator">=</span><span class="string">"sdc"</span> bus<span class="operator">=</span><span class="string">"virtio"</span>/></span><br><span class="line"></disk></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"><interface <span class="built_in">type</span>=<span class="string">"bridge"</span>></span><br><span class="line"> <mac address=<span class="string">"00:00:00:00:00:00"</span>/></span><br><span class="line"> <<span class="built_in">source</span> bridge=<span class="string">"br-routed"</span>/></span><br><span class="line"> <target dev=<span class="string">"vnet0"</span>/></span><br><span class="line"> <model <span class="built_in">type</span>=<span class="string">"virtio"</span>/></span><br><span class="line"> <<span class="built_in">alias</span> name=<span class="string">"net0"</span>/></span><br><span class="line"> <address <span class="built_in">type</span>=<span class="string">"pci"</span> domain=<span class="string">"0x0000"</span> bus=<span class="string">"0x0a"</span> slot=<span class="string">"0x00"</span> <span class="keyword">function</span>=<span class="string">"0x0"</span>/></span><br><span class="line"></interface></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 + ~/.local/share/kio/servicemenus/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">"%F"</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">"<span class="variable">$1</span>"</span>)</span><br><span class="line"><span class="built_in">ln</span> -s <span class="string">"<span class="variable">$NAME</span>"</span> <span class="string">"<span class="variable">$NAME</span>.lnk"</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=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">'quiet splash split_lock_detect=off'</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="迁移系统&修复引导"></a>迁移系统&修复引导</h3><ol><li>进入 livecd,使用 Gparted 复制系统到新硬盘</li><li>修改新硬盘分区的 /etc/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> {proc,dev/sys}; <span class="keyword">do</span></span><br><span class="line"> mount --<span class="built_in">bind</span> /<span class="variable">${i}</span> /mnt/<span class="variable">${i}</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">${dev_path}</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, 位置格式 ${dev_path}/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 <proxy-server></code></li><li>设置某个域名的代理:<code>Acquire::http::proxy::<domain> <proxy-server|DIRECT></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">"http://yourproxyaddress:proxyport"</span>;</span><br><span class="line">#Acquire::https::Proxy <span class="string">"http://yourproxyaddress:proxyport"</span>;</span><br><span class="line">#Acquire::ftp::Proxy <span class="string">"http://yourproxyaddress:proxyport"</span>;</span><br><span class="line">#Acquire::socks::Proxy <span class="string">"http://yourproxyaddress:proxyport"</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">"DIRECT"</span>;</span><br><span class="line">#Acquire::http::proxy::HOST_NAME_TO_BE_PROXIED <span class="string">"http://yourproxyaddress:proxyport"</span></span><br><span class="line"></span><br><span class="line"># 黑名单模式示例</span><br><span class="line">Acquire::https::Proxy <span class="string">"http://localhost:10808"</span>;</span><br><span class="line">Acquire::https::Proxy::<span class="string">"mirrors.aliyun.com"</span> <span class="string">"DIRECT"</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/{user,system}.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 > /dev/null <<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"> && <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 's|Exec=/usr|Exec=env QT_IM_MODULE=fcitx /usr|' \</span><br><span class="line">/usr/share/applications/wechat.desktop > ~/.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">"Hidden=true"</span> >> ~/.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>保存时机是注销/关机时,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/2G 够了,而 ubuntu 24 默认是 10%/20%,在大内存上有点丧心病狂了。<br>如果主要使用机械硬盘,还可以考虑再调小些。</p><h3 id="修改文件句柄数量限制"><a href="#修改文件句柄数量限制" class="headerlink" title="修改文件句柄数量限制"></a>修改文件句柄数量限制</h3><p>默认限制是 1024, 文件句柄数量太小会导致应用异常,比如 vscode 无法监控文件变更</p><p>修改 /etc/security/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>编辑 /etc/systemd/system.conf 或者 /etc/systemd/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"><p>这些年日常操作系统一直是 windows 和 macOS 交替使用,Linux 一般只作为服务器的操作系统。<br>然而,咖喱味的 Windows 11 (LTSC)用起来实在难受(平时只玩游戏,下载)<br>arm 的 macbook 虽然能效惊人,但是内存金子价格,软件也封闭(点名finder)和傲慢,实在受不了。<br>最后,还是转向 Linux,毕竟现在 wayland 基本堪用,国产软件也随着信创逐渐丰富了。</p></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/macOS/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>设置分辨率:控制中心 -> 显示器 -> 设置为 1920x1080</li><li>关闭自动睡眠:控制中心 -> 电源管理 -> 动作和显示修改为<code>从不</code></li><li>关闭屏幕保护:控制中心 -> 屏幕保护程序 -> 取消勾选所有选项</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">'DISPLAY'</span>]</span> = <span class="string">':0'</span></span><br><span class="line">os<span class="selector-class">.environ</span><span class="selector-attr">[<span class="string">'XAUTHORITY'</span>]</span> = <span class="string">'/home/<your-name>/.Xauthority'</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'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">"<span class="variable">$@</span>"</span> >> /tmp/gnome-screenshot.log 2>&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">'--psm 8 -c tessedit_char_whitelist=0123456789'</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">'region.png'</span></span>):</span><br><span class="line"> url = <span class="string">'http://your-address:8000/ocr'</span></span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(file, <span class="string">'rb'</span>) <span class="keyword">as</span> fp:</span><br><span class="line"> files = {</span><br><span class="line"> <span class="string">'file'</span>: fp,</span><br><span class="line"> }</span><br><span class="line"> data = {</span><br><span class="line"> <span class="string">'probability'</span>: <span class="string">'false'</span>,</span><br><span class="line"> <span class="string">'png_fix'</span>: <span class="string">'false'</span>,</span><br><span class="line"> <span class="string">'charsets'</span>: <span class="string">'0123456789'</span> <span class="comment"># 验证码可能的字符列表</span></span><br><span class="line"> }</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">'data'</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">"SOME_VAR=foobar python your-script.py"</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">'linux'</span>:</span><br><span class="line"> <span class="keyword">return</span> subprocess.check_call([<span class="string">'wmctrl'</span>, <span class="string">'-a'</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"can not find <span class="subst">{image}</span>!"</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'wait <span class="subst">{image}</span> failed'</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"><p>PyAutoGUI 是 GUI 功能强大自动化方案,但 UI 程序的运行环境选择与配置也是一大难题。</p></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>所以,神话中的言出法随也不难理解,毕竟人是观念的动物,你改变/植入了某人的某个观念,那么你就改变了他眼中的世界。<br>这也就是《乔布斯传》中乔布斯的扭曲现实力场。</p>]]></content>
<summary type="html"><p>语言的力量远比想象的强大,某种程度上是有虚空造物的能力。</p></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"><p>联想某些版本的 bios 似乎会禁止降级,即使打开 bios 设置里的允许降级选项,依然会提示 “this platform does not support IHISI interface” 的错误,导致降级失败。</p></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 "%d\n" 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"><p>使用 redroid 等安卓虚拟环境,可能会发现 google play 用不了的问题。<br>虽然系统集成了 gapps,但系统提示 “设备未获得play保护机制认证”,无法登录 play 商店。<br>可能的原因比较多,这里大概是因为虚拟机的型号没在google的数据库里。<br>解决方案就是,获取 GSF ID 注册到 Google。</p></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">&audio</span>-codec=aac<span class="variable">&video</span>-bit-rate=16M<span class="variable">&video</span>-codec=h264<span class="variable">&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>关闭导航栏:系统 > 手势 > 系统导航 > 手势导航</li><li>加快或者关闭系统动画:开发者选项 > 窗口动画缩放、过渡动画缩放、Animator 时长比例 > 关闭或者0.5x</li></ol>]]></content>
<summary type="html"><p>有些应用在安卓上是独占的,iOS 上又没有比较好的替代品,而且 iOS 上没有能用安卓模拟器。<br>如果使用多个设备,维护的心智成本又高,被这个问题困扰了许久。</p>
<p>最近碰巧了解了 <a href="https://github.com/Genymobile/scrcpy">scrcpy</a>, 用于远程控制安卓,终于解决了这个问题。</p></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">"leetcode.useEndpointTranslation"</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">"leetcode.workspaceFolder"</span><span class="punctuation">:</span> <span class="string">"/Users/<your_name>/projects/vscode-leetcode-cn-rust"</span><span class="punctuation">,</span> </span><br><span class="line"><span class="attr">"leetcode.filePath"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"default"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"folder"</span><span class="punctuation">:</span> <span class="string">"src/solutions"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"filename"</span><span class="punctuation">:</span> <span class="string">"${id}_${snake_case_name}.${ext}"</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</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: &<span class="type">str</span> = <span class="string">"sdfsdfsd.rs"</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 {</span><br><span class="line"> automod::dir!(<span class="string">"src/solutions"</span>);</span><br><span class="line">}</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">"runOnSave.commands"</span>: [</span><br><span class="line"> {</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">"command"</span>: <span class="string">"sh onsave.sh <span class="subst">${fileBasename}</span>"</span>, </span><br><span class="line"> <span class="string">"runIn"</span>: <span class="string">"backend"</span>,</span><br><span class="line"> <span class="string">"finishStatusMessage"</span>: <span class="string">"touched <span class="subst">${workspaceFolderBasename}</span>"</span></span><br><span class="line"> },</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">"<span class="variable">$FILE</span>"</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">"/CURRENT/c\const CURRENT: &str = \"<span class="variable">$FILE</span>\";"</span> src/lib.rs</span><br><span class="line"> <span class="keyword">if</span> [[ `grep <span class="string">'::dir!(pub'</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">"s|::dir!(pub |::dir!(|"</span> src/lib.rs</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> gsed -i <span class="string">"s|::dir!(|::dir!(pub |"</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>"// @lc code=end"</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() {</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">"()[]{}"</span>));</span><br><span class="line"> // println!(<span class="string">"RESUTL\t{:?}"</span>, res);</span><br><span class="line">}</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"><p>本文介绍在 VSCode 中配置和使用插件来高效地解决 LeetCode 问题,并使用 Rust 语言编写和测试代码。</p></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"><img <span class="attribute">src</span>=<span class="string">"https://bank.com/transfer?amount=1000&to=attackerAccount"</span> </span><br><span class="line"> <span class="attribute">style</span>=<span class="string">"display:none;"</span>></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><script>、<img>、<link></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><script></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"><ul>
<li>什么是跨域?<ul>
<li>就是当前域访问了非本域的资源。对于http来说,url代表资源,也就是访问了非本域的url。</li>
</ul>
</li>
</ul></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 && 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="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api"</span><br><span class="line">export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles"</span><br><span class="line">export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"</span><br><span class="line">export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git"</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 "$(~/.homebrew/bin/brew shellenv)"</span><br><span class="line">brew update --force --quiet</span><br><span class="line">chmod -R go-w "$(brew --prefix)/share/zsh"</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 'eval "$(~/.homebrew/bin/brew shellenv)"' >> ~/.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"><p>一些公用的 Linux 服务器,处于维护以及安全考虑,一般只会提供普通权限用户给使用者。<br>普通用户的权限满足日常使用是够了,但是难以配置自己的开发环境,安装一些自己需要的包。</p>
<p>如果都从源码编译安装软件,依赖的维护过于复杂,初始编译工具链的版本可能也不满足需求,如 gcc 版本过低。<br>如果申请 sudo 权限或者请求更新系统或安装 docker,后期责任难以界定,运维和管理员一般也不会同意。</p>
<p>所以,最优方案还是有需求的用户在个人目录维护自己的工具链和环境。下文方案为围绕 HomeBrew 构建。</p></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">"~/weiyun_sync/!sync/logseq-note/pages/生词本.md"</span>)</span><br><span class="line">output = []</span><br><span class="line">text = sys.argv[<span class="number">1</span>].decode(<span class="string">'utf8'</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) < <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">'*'</span>:</span><br><span class="line"> output.append(<span class="string">'- {}\t{} [[card]]'</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">'- {}\t [[card]]'</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">'\t- {}'</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">'\t '</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">'r'</span>, encoding=<span class="string">'utf8'</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">'-'</span>) <span class="keyword">and</span> <span class="built_in">len</span>(parts) > <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">'a'</span>, encoding=<span class="string">'utf8'</span>) <span class="keyword">as</span> fp:</span><br><span class="line"> fp.write(<span class="string">'\n'</span>)</span><br><span class="line"> fp.write(<span class="string">'\n'</span>.join(output))</span><br><span class="line"> fp.write(<span class="string">'\n'</span>)</span><br><span class="line"> subprocess.check_call([<span class="string">'osascript'</span>, <span class="string">'-e'</span>, <span class="string">u'display notification "添加 {}" with title "生词本"'</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">'osascript'</span>, <span class="string">'-e'</span>, <span class="string">u'display notification "跳过 {}" with title "生词本"'</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/<用户名>/.config/karabiner/karabiner.json</code></p><p>在 <code>profiles -> complex_modifications -> 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">{</span></span><br><span class="line"> <span class="attr">"description"</span><span class="punctuation">:</span> <span class="string">"Mouse"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"manipulators"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"from"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"pointing_button"</span><span class="punctuation">:</span> <span class="string">"button5"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"to"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"pointing_button"</span><span class="punctuation">:</span> <span class="string">"button1"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"pointing_button"</span><span class="punctuation">:</span> <span class="string">"button1"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"key_code"</span><span class="punctuation">:</span> <span class="string">"d"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"modifiers"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="string">"left_command"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"left_control"</span></span><br><span class="line"> <span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"key_code"</span><span class="punctuation">:</span> <span class="string">"1"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"modifiers"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="string">"left_option"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"left_shift"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="string">"left_command"</span></span><br><span class="line"> <span class="punctuation">]</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"type"</span><span class="punctuation">:</span> <span class="string">"basic"</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">}</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"><p>macOS 系统的自带词典应用非常强大,与其他应用整合很好,快捷取词很方便(command+control+d)。<br>但是美中不足的是缺少生词本功能,查了单词又很容易忘记,对语言学习者来说就有些不便了。</p>
<p>经过本强迫症的探索,终于找到基于 Karabiner-Elements + Automator + Logseq 的完美生词本方案。<br>最后的效果是,快捷键取词的同时记录单词卡片到Logseq对应的笔记。</p></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">'127.0.0.1'</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">"python -c 'import time;time.sleep(1000)' "</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=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=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'lsof -p <span class="subst">{os.getpid()}</span>'</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'lsof -p <span class="subst">{os.getpid()}</span>'</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">"python -c 'import time;time.sleep(1000)'"</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"><p>我们在测试代码时,由于需要经常重启服务,经常会发现服务端口被占用。<br>一般kill掉后台进程就ok了,但是如果服务有启动一些常驻的后台程序,可能也会导致端口不能释放。</p>
<p>在类UNIX系统中,一切被打开的文件、端口被抽象为文件描述符(file descriptor)<br>从python3.4开始,文件描述符默认是non-inheritable,也就是子进程不会共享文件描述符。</p></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=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 /)来控制。</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 && \</span></span><br><span class="line"><span class="language-bash"> yum -y install supervisor && \</span></span><br><span class="line"><span class="language-bash"> yum -y clean all && <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">"/usr/bin/supervisord"</span>, <span class="string">"-n"</span>, <span class="string">"-c"</span>, <span class="string">"/etc/supervisord.conf"</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> <siginal handler> <signal 1> <signal 2> ...</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 &</span><br><span class="line"></span><br><span class="line"><span class="built_in">trap</span> <span class="string">"supervisorctl stop all && sleep 3"</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">"sh"</span>, <span class="string">"/root/init.sh"</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"><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></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>
</feed>