<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>bisko.be - 开发</title><description>技术、开发、硬件与实验。</description><link>https://bisko.be/zh/posts/dev/</link><language>zh-Hans</language><item><title>在 CachyOS 上用老显卡运行 Ollama</title><link>https://bisko.be/zh/posts/dev/running-ollama-with-older-gpus-on-cachyos/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/running-ollama-with-older-gpus-on-cachyos/</guid><description>今天我把 Windows 11 虚拟机迁移到了 Linux,好让上面跑的服务更灵活,结果遇到了个问题:Ollama 没法在我的老显卡 GTX1070 和 GTX970 上运行</description><pubDate>Sat, 28 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3 id=&quot;太长不看&quot;&gt;太长不看;&lt;/h3&gt;
&lt;p&gt;CachyOS 上的 &lt;code&gt;ollama-cuda&lt;/code&gt;(截至目前)只支持 &lt;code&gt;compute &gt;= 7.5&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果你的显卡比较老、&lt;code&gt;compute&lt;/code&gt; 版本更低,那你就必须二选一:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从源码编译 &lt;code&gt;ollama-cuda&lt;/code&gt;,并加上对旧 compute 目标的支持&lt;/li&gt;
&lt;li&gt;安装 &lt;code&gt;ollama-vulkan&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我选了 &lt;code&gt;ollama-vulkan&lt;/code&gt;,运行得完美无缺!&lt;/p&gt;
&lt;h3 id=&quot;故事时间&quot;&gt;故事时间&lt;/h3&gt;
&lt;p&gt;今天我灵光一闪,决定终于把 Windows 11 虚拟机关掉,迁移到 Linux 上。&lt;/p&gt;
&lt;p&gt;需求是要能运行:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ollama - 用来玩本地模型的魔法&lt;/li&gt;
&lt;li&gt;Docker - 用于远程开发&lt;/li&gt;
&lt;li&gt;Steam - 用来打游戏&lt;/li&gt;
&lt;li&gt;Jellyfin - 用来存放家庭视频档案&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里我只聊 Ollama,因为这段经历挺有意思,也让我琢磨为什么事情不能更直观一点。&lt;/p&gt;
&lt;p&gt;这台机器里有 2 块显卡:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GTX 1070 - 8GB&lt;/li&gt;
&lt;li&gt;GTX 970 - 4GB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我想在它们上面跑 Ollama,折腾一下本地模型,看看怎么把它集成进一些项目里。比如:在本地对数据做匿名化、按我的使用场景搭一个非破坏性的 OpenClaw 实现、对手机扫描的文档做本地 OCR、自动化处理家里日常管理的一些事情。&lt;/p&gt;
&lt;p&gt;所以我很自然地装上了 &lt;code&gt;ollama&lt;/code&gt; 和 &lt;code&gt;ollama-cuda&lt;/code&gt;,毕竟里面有 2 块 Nvidia 显卡,我以为肯定没问题,毕竟几分钟前它在 Windows 上还跑得好好的。&lt;/p&gt;
&lt;p&gt;我启动了 Ollama,加载 &lt;code&gt;qwen3:8b&lt;/code&gt; 做个快速实验,然后……它居然在用 CPU 跑 🤔&lt;/p&gt;
&lt;p&gt;显卡的显存明明完全装得下,可不知为什么 Ollama 偏偏选择用 CPU 来跑。&lt;/p&gt;
&lt;p&gt;我试着从日志里找原因,后来请 Claude 帮了把手,它给出了一个相当让人头大的结论:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;The&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; logs&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; tell&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; story&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; clearly.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Both&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; your&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; GPUs&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; are&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; detected&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; but&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; then&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; filtered&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; out&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; because&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; they&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; fail&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CUDA&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; initialization:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  &quot;filtering device which didn&apos;t fully initialize&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; id=GPU-eb132a13-b9f4-40e3-d85d-60f211dfd21e&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ...&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; library=CUDA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  &quot;filtering device which didn&apos;t fully initialize&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; id=GPU-550263c8-37e2-2a25-c0b6-10cc7511ed20&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ...&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; library=CUDA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  The&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reason&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CUDA&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; library&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; bundled&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Ollama&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0.17.4&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; was&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; compiled&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; these&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; architectures&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; only:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  CUDA.0.ARCHS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;=750,800,860,870,880,890,900,1000,1030,1100,1200,1210&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  The&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; minimum&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; supported&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; compute&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; capability&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 7.5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (Turing &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; RTX&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 20xx&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; series&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;). Your GPUs are older than that:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  ┌──────────┬────────────────────┬──────────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   GPU&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Compute&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Capability&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Architecture&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  ├──────────┼────────────────────┼──────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; GTX&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1070&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; │&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 6.1&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Pascal&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;       │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  ├──────────┼────────────────────┼──────────────┤&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; GTX&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 970&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  │&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 5.2&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                │&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Maxwell&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  └──────────┴────────────────────┴──────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  Neither&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; supported&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; list,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; so&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CUDA&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; initialization&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; fails&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; both,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Ollama&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; falls&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; back&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CPU-only&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; inference.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  Your&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; options:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  1.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Downgrade&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Ollama&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; an&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; older&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; version&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; that&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; still&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; included&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Pascal/Maxwell&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; support&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (pre-0.6.x &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;era&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; had&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; broader&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; arch&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; coverage,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; but&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; check&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; release&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; notes&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; specifics&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  2.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Build&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Ollama&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; source&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CUDA_ARCHITECTURES=&quot;52;61;75;80;86&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; include&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; your&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; GPU&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; architectures.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;  3.&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Upgrade&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; GPUs&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; to&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; at&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; least&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; an&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; RTX&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 2060&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; or&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; newer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (compute &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;7.5+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;).&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说实话,这让我有点失望,因为在换新显卡之前,我想尽可能把这些旧卡榨干。&lt;/p&gt;
&lt;p&gt;我真不想从源码重新编译 Ollama,它本来就该开箱即用,而且我很困惑它为什么不行,毕竟在 Windows 上安装的版本是支持这些架构的。&lt;/p&gt;
&lt;p&gt;这些显卡在 Ollama 官网上也被列为受支持的!&lt;/p&gt;
&lt;p&gt;长话短说,我在 Google 上折腾了一圈之后发现:&lt;/p&gt;
&lt;p&gt;原来 &lt;code&gt;ollama-cuda&lt;/code&gt; 就只满足上面列出的那些要求……而给出的建议是安装 &lt;code&gt;ollama-vulkan&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;装上之后,一切都顺滑地跑起来了!&lt;/p&gt;</content:encoded><category>linux</category><category>llm</category><category>ollama</category><category>cachyos</category><category>vulcan</category><category>cuda</category></item><item><title>[Linux] 恢复断开连接的 `apt dist-upgrade` 会话</title><link>https://bisko.be/zh/posts/dev/linux-recovering-a-disconnected-apt-dist-upgrade-session/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/linux-recovering-a-disconnected-apt-dist-upgrade-session/</guid><description>今天我通过内置控制台远程更新我的 Proxmox,但中途忘了自己在干什么,直接把标签页关掉了。结果就是我再也连不上了:软件包已经在后台更新,而升级过程弹出了一个是否替换文件的问题,正在等我回答</description><pubDate>Wed, 23 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今天我通过内置控制台远程更新我的 Proxmox,但中途忘了自己在干什么,直接把标签页关掉了。&lt;/p&gt;
&lt;p&gt;结果就是我没法再连上去了:软件包已经在后台更新,而升级过程弹出了一个是否替换文件的问题,正在等我输入。&lt;/p&gt;
&lt;p&gt;所以我虽然能 SSH 进去,却什么也做不了,因为它还在一个我连不上的控制台上等待输入,而那个界面根本打不开。&lt;/p&gt;
&lt;p&gt;四处查了一下之后,我偶然发现了一个 &lt;a href=&quot;https://serverfault.com/questions/19634/how-to-reconnect-to-a-disconnected-ssh-session&quot;&gt;Serverfault 帖子&lt;/a&gt;,里面提到可以用 &lt;code&gt;reptyr&lt;/code&gt; 通过 SSH 接管终端会话。&lt;/p&gt;
&lt;p&gt;这里记录一下具体做了什么,以便把会话拿回来并完成升级:&lt;/p&gt;
&lt;p&gt;来自&lt;a href=&quot;https://serverfault.com/questions/19634/how-to-reconnect-to-a-disconnected-ssh-session#comment1506308_979180&quot;&gt;这条评论&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://github.com/nelhage/reptyr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; reptyr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;make&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就在机器上安装好了 &lt;code&gt;reptyr&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然后我用 &lt;code&gt;ps ax | grep pts&lt;/code&gt; 查找所有打开的会话,找到我要的那个进程。&lt;/p&gt;
&lt;p&gt;接着我在调用树中找到分配了 &lt;code&gt;pts&lt;/code&gt; 的最顶层父进程,并通过它的 PID 附加上去:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;reptyr&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -T&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1390511&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后我就能完成升级了!🎉&lt;/p&gt;</content:encoded><category>Uncategorized</category></item><item><title>[Linux] Syncthing – 以服务方式运行时出现只读文件系统错误</title><link>https://bisko.be/zh/posts/dev/linux-syncthing-read-only-file-system-when-running-as-service/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/linux-syncthing-read-only-file-system-when-running-as-service/</guid><description>我用 Syncthing 把工作副本同步到本地开发服务器已经有好几个月了。一开始我是登录后通过命令行手动运行它的,一直好好的,所以后来改成以服务方式运行时,我也没多想。</description><pubDate>Wed, 06 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我用 &lt;a href=&quot;https://syncthing.net/&quot;&gt;Syncthing&lt;/a&gt; 把工作副本同步到本地开发服务器已经有好几个月了。&lt;/p&gt;
&lt;p&gt;一开始我是登录后通过命令行手动运行它的。一切正常,所以后来改成以服务方式运行时,我也没多想。它启动了,也同步了一些数据,一切看起来都很好。&lt;/p&gt;
&lt;p&gt;当然,直到出问题为止!🙂&lt;/p&gt;
&lt;p&gt;原来日志里冒出了一堆错误,说开发服务器上的文件系统是只读的,导致 Syncthing 什么都同步不了,而我完全没注意到。&lt;/p&gt;
&lt;p&gt;我还以为是个 bug,只是文件夹显示为 &lt;code&gt;Out of Sync&lt;/code&gt;(不同步),实际上还是在同步的。结果我想错了。&lt;/p&gt;
&lt;p&gt;折腾了好几个小时,在论坛里搜了好几轮 Google 也没找到任何线索,最后我找到了 &lt;a href=&quot;https://wiki.archlinux.org/title/syncthing#read-only_file_system_error_on_/etc_although_run_as_root&quot;&gt;ArchWiki 上关于 Syncthing 的文章&lt;/a&gt;,里面提到的正是我遇到的情况:&lt;/p&gt;
&lt;p&gt;read-only file system error on /etc although run as root&lt;/p&gt;
&lt;p&gt;这和我的情况一模一样,只不过出问题的文件夹不是 &lt;code&gt;/etc&lt;/code&gt;,而是 &lt;code&gt;/usr/local/src&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;读完之后我才明白:如果你通过 &lt;code&gt;systemd&lt;/code&gt; 运行 Syncthing(或者任何东西),并且把服务的 &lt;code&gt;ProtectSystem&lt;/code&gt; 设成了 &lt;code&gt;full&lt;/code&gt;,那么某些目录默认就会变成只读,&lt;code&gt;/usr&lt;/code&gt; 就是其中之一。&lt;/p&gt;
&lt;p&gt;要解决这个问题,你需要在服务配置里加一行,然后重启服务(别忘了先 &lt;code&gt;daemon-reload&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReadWritePaths=/usr/local/src&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这样之后 Syncthing 就能正常工作了。&lt;/p&gt;</content:encoded><category>Homelab</category><category>linux</category><category>permissions</category><category>read-only</category><category>syncthing</category><category>systemd</category></item><item><title>[Linux] 在多台服务器上并行执行多条命令</title><link>https://bisko.be/zh/posts/dev/linux-running-multiple-commands-on-multiple-servers-in-parallel/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/linux-running-multiple-commands-on-multiple-servers-in-parallel/</guid><description>我家里有一个跑着 Docker Swarm 的树莓派集群。为了方便维护,它们使用相同的配置——用户、Docker 版本等等。有时候某些更新需要在集群的所有节点上执行同样的操作,比如创建一个新的 Docker macvlan 网络。要做到这一点,</description><pubDate>Mon, 19 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我家里有一个跑着 Docker Swarm 的树莓派集群。为了方便维护,它们使用相同的配置——用户、Docker 版本等等。&lt;/p&gt;
&lt;p&gt;有时候某些更新需要在集群的所有节点上执行同样的操作,比如创建一个新的 Docker macvlan 网络。&lt;/p&gt;
&lt;p&gt;要做到这一点,要么就得在所有节点上手动执行相同的命令:挨个 &lt;strong&gt;ssh&lt;/strong&gt; 进去,跑完命令,再换下一台。&lt;/p&gt;
&lt;p&gt;既然我想着从长远来看少干点活、把一切弄得尽可能井井有条,我就开始研究怎么同时在所有节点上执行命令。&lt;/p&gt;
&lt;h3 id=&quot;第一轮自己写-python-脚本&quot;&gt;第一轮:自己写 Python 脚本&lt;/h3&gt;
&lt;p&gt;第一次尝试是自己写一个 Python 脚本。一方面是想顺便把 Python 学得更扎实一点,另一方面是我找不到一种更原生的方式来执行命令。&lt;/p&gt;
&lt;p&gt;可想而知(嘿,我对这门语言还没多少经验 🙂),它很快就变成了一团臃肿的乱麻,而且特别容易出问题。&lt;/p&gt;
&lt;p&gt;我得找个别的办法,不用再去重新理解三周前写的东西,也不用再琢磨上次到底是什么搞崩了。&lt;/p&gt;
&lt;h3 id=&quot;第二轮pssh&quot;&gt;第二轮:&lt;code&gt;pssh&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;四处搜索的时候,我偶然发现了 &lt;code&gt;[pssh](https://linux.die.net/man/1/pssh)&lt;/code&gt;,它可以通过 SSH 并行执行任务。&lt;/p&gt;
&lt;p&gt;总的来说,把它跑起来还挺容易的。&lt;/p&gt;
&lt;p&gt;问题在于,有些操作需要通过 &lt;code&gt;sudo&lt;/code&gt; 来执行。到处传递密码可不是什么好主意,所以我又得去找办法,弄清楚怎么在远程服务器要求输入密码时把 &lt;code&gt;sudo&lt;/code&gt; 密码发过去。&lt;/p&gt;
&lt;p&gt;结果发现,只要你碰巧看到对的 &lt;a href=&quot;https://unix.stackexchange.com/a/487558/134974&quot;&gt;StackOverflow 评论&lt;/a&gt;,这事儿就简单得很。&lt;/p&gt;
&lt;h3 id=&quot;太长不看版&quot;&gt;太长不看版:&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stty&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -echo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;printf&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Password: &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;read&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; PASS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stty&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; echo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;${&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;PASS&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pssh&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -h&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;HOSTFIL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;E&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; /tmp/output&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -t&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -I&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;`&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;cat&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ./commands.sh`&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-I &quot;`cat ./commands.sh`&quot;&lt;/code&gt; 必须写成这样(用引号和反引号包起来),否则命令无法正确执行。密码会作为单独的一条命令发送,而不会被当作 &lt;strong&gt;sudo&lt;/strong&gt; 的输入。&lt;/p&gt;
&lt;p&gt;另一个要注意的地方是,由于这样的设置方式,执行 &lt;code&gt;sudo&lt;/code&gt; 时需要加上 &lt;code&gt;-S&lt;/code&gt; 参数,才能正确读取密码输入。&lt;/p&gt;</content:encoded><category>Uncategorized</category></item><item><title>仅用命令行工具猜解纯数字 ZIP 文件密码</title><link>https://bisko.be/zh/posts/dev/guessing-a-numeric-zip-file-password-only-with-command-line-utilities/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/guessing-a-numeric-zip-file-password-only-with-command-line-utilities/</guid><description>今天我收到了网络服务商的月度账单，有件事让我觉得挺奇怪。邮件里附带的账单是一个带密码的 ZIP 文件。先不说在当下这个年代（垃圾邮件、恶意软件、钓鱼攻击满天飞）收到一个 ZIP 格式的附件本身就很诡异，他们选的密码也弱得让人吃惊，</description><pubDate>Mon, 14 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今天我收到了网络服务商的月度账单，有件事让我觉得挺奇怪。&lt;/p&gt;
&lt;p&gt;邮件里附带的账单是一个带密码的 ZIP 文件。&lt;/p&gt;
&lt;p&gt;先不说在当下这个年代（垃圾邮件、恶意软件、钓鱼攻击满天飞）收到一个 ZIP 格式的附件本身就很诡异，他们选的密码也弱得让人吃惊。&lt;/p&gt;
&lt;p&gt;密码是账户持有人的生日，格式为 &lt;code&gt;YYMMDD&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我在健身房的时候，脑子里粗略算了一下，发现可能的组合数其实少得可怜。用了个在线工具算出从 1900-01-01 到现在经过了多少天（因为不是所有组合都能对应一个有效日期）之后，发现组合数一共只有 44,075 种（截止到 2020-09-14）。&lt;/p&gt;
&lt;p&gt;我知道那些用 GPU 算力跑哈希破解的优化工具，每秒能猜几百万次。但我当时只能在健身房用一台 VPS，所以我想看看，不借助这些工具、只用 Linux/macOS 的命令行，能多快猜出密码。&lt;/p&gt;
&lt;p&gt;我写出了下面这段代码，可以在 macOS 上运行：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;echo {1..44000} | xargs -P 16 -n 1 -I{} date -v &quot;-{}d&quot; +&quot;%y%m%d&quot; | xargs -P 16 -I{} sh -c &apos;unzip -qqoP &quot;{}&quot; 0417160508.zip &amp;#x26;&amp;#x26; echo &quot;{}&quot; &amp;#x26;&amp;#x26; exit 255&apos;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这条命令看起来挺复杂，但原理其实很简单。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;echo {1..44000}&lt;/code&gt; 生成从 1 到 44,000 的数字，传给下一步。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xargs -P 16 -n 1 -I{} date -v &quot;-{}d&quot; +&quot;%y%m%d&quot;&lt;/code&gt; —— 接收第一步的输入，利用 &lt;code&gt;date&lt;/code&gt; 的功能（给日期“加减”特定天数）生成 &lt;code&gt;YYMMDD&lt;/code&gt; 格式的日期。这里是从当前日期往回减去 X 天，X 就是第一步生成的数字。这样就能一路生成从今天到 1900 年代的日期。&lt;/p&gt;
&lt;p&gt;第三步是最有意思的部分，我把它拆成两块来说：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xargs -P 16 -I{} sh -c &apos;...&apos;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这一步拿第二步生成的日期，对每个日期执行一条命令。&lt;code&gt;-P 16&lt;/code&gt; 表示用 16 个并行进程来跑，这样处理速度更快。第二步生成日期时也用了同样的技巧来提速。&lt;/p&gt;
&lt;p&gt;第二部分才是真正猜密码的地方：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;unzip -qqoP &quot;{}&quot; 0417160508.zip &amp;#x26;&amp;#x26; echo &quot;{}&quot; &amp;#x26;&amp;#x26; exit 255&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这段代码先尝试用第二步生成的密码解压 ZIP 文件。如果成功了，就会打印（&lt;code&gt;echo&lt;/code&gt;）这次猜中的密码，然后 &lt;code&gt;exit 255&lt;/code&gt; 强制让 &lt;code&gt;xargs&lt;/code&gt; 停止运行。毕竟密码已经找到了，没必要继续猜下去。&lt;/p&gt;
&lt;p&gt;总而言之，拿这个当作文件密码实在是个相当糟糕的决定，因为运行上面这条命令，大概只花了 10 秒钟就猜出了我账单的正确密码。换成别人来猜，估计也快不了多少。&lt;/p&gt;</content:encoded><category>Uncategorized</category></item><item><title>处理 PHP 中的意大利面条式代码:用匿名函数提取模板代码片段</title><link>https://bisko.be/zh/posts/dev/dealing-with-spaghetti-code-in-php-using-anonymous-functions-to-extract-pieces-of-template-code/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/dealing-with-spaghetti-code-in-php-using-anonymous-functions-to-extract-pieces-of-template-code/</guid><description>最近我在帮一个朋友理清一些代码,并研究怎么改才能很好地配合对后端的异步调用。如果还能顺便写得更模块化、更可复用,那就更好了。这些代码是一个 WordPress 插件,写的时候</description><pubDate>Sat, 08 Aug 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近我在帮一个朋友理清一些代码,并研究怎么改才能很好地配合对后端的异步调用。如果还能顺便写得更模块化、更可复用,那就更好了。&lt;/p&gt;
&lt;p&gt;这些代码是一个 WordPress 插件,用的是非常老派的写法:业务逻辑和前端逻辑混在一起,数据处理就夹在模板代码中间,诸如此类。&lt;/p&gt;
&lt;p&gt;基本上就是你在 21 世纪初的 PHP 站点里见过的那种东西,那时候这门语言还没有那些很棒的 OOP 特性,也没有一堆成熟的框架。&lt;/p&gt;
&lt;p&gt;我今天啃的这个场景是这样的:有一个表格,展示一堆条目。我们需要给表格加几个筛选器,并且通过 AJAX 来工作,这样就能快速刷新 UI,而不用重新加载整个 WordPress 后台页面。&lt;/p&gt;
&lt;p&gt;筛选数据很简单:写一个内联的 &lt;code&gt;array_filter&lt;/code&gt;,在数据传给模板代码之前先过滤一下(为了保持同样的代码风格,这段也写在模板文件里 😇)。&lt;/p&gt;
&lt;p&gt;问题出在我们开始让表格能通过 AJAX 渲染的时候。&lt;/p&gt;
&lt;p&gt;表格夹在一大堆其他模板代码和业务代码中间。如果想让 AJAX 只输出表格,我们只有几种办法。&lt;/p&gt;
&lt;p&gt;第一种办法,是把表格前后的 HTML 都包在巨大的 &lt;code&gt;if&lt;/code&gt; 语句里,确保它们不会被渲染出来。&lt;/p&gt;
&lt;p&gt;另一种办法,是把模板抽到单独的文件里,然后在我们于插件代码中定义的 AJAX action 里 include 它。&lt;/p&gt;
&lt;p&gt;匿名函数来救场!&lt;/p&gt;
&lt;p&gt;我琢磨着怎么用最简单的方式搞定这件事,就试着定义一个普通的 PHP 函数,把表格代码放进去,但这里有个问题。&lt;/p&gt;
&lt;p&gt;这段代码运行在一个类的方法里,表格模板中有对 &lt;code&gt;$this&lt;/code&gt; 的引用。如果不想大改代码,我们就得把 &lt;code&gt;$this&lt;/code&gt; 当作函数参数传进去。可惜这行不通,因为你没法把 &lt;code&gt;$this&lt;/code&gt; 当参数传 🤷‍♂️。&lt;/p&gt;
&lt;p&gt;我有点失望,因为这意味着模板里要重写一堆东西,而且有不少引用我们都得顾及到,才能保证它继续正常工作。&lt;/p&gt;
&lt;p&gt;然后我想起来,这个站点跑的不是 PHP5.2,而是 PHP7,支持匿名函数。&lt;/p&gt;
&lt;p&gt;匿名函数还有个很好用的特性:你可以指定调用时,从调用方作用域里把哪些变量传给这个函数。比如:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;php&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$table_function &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ( $pages, $current_page, $items ) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ... Table render code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样你就可以在函数体里直接引用 &lt;code&gt;$pages&lt;/code&gt; 或 &lt;code&gt;$items&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;最棒的是,你还可以把 &lt;code&gt;$this&lt;/code&gt; 也作为变量传进函数作用域!&lt;/p&gt;
&lt;p&gt;于是我们就能把表格渲染代码完整地抽到一个单独的函数里,在 AJAX 调用时单独调用它了!&lt;/p&gt;
&lt;p&gt;当然,函数体里用到的变量必须是已定义的,所以一定要小心 AJAX 函数能在哪里被调用。&lt;/p&gt;
&lt;p&gt;对我们来说,这意味着会在主类里加一个很小的 action,它仍然会 require 那个文件,但我们能更好地控制并关掉其他所有会产生的输出。&lt;/p&gt;</content:encoded><category>Snippets</category><category>lambda-functions</category><category>php</category><category>WordPress</category></item><item><title>Arduino/ESP32:禁用任务看门狗</title><link>https://bisko.be/zh/posts/dev/arduino-esp32-disabling-the-task-watchdog/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/arduino-esp32-disabling-the-task-watchdog/</guid><description>最近我一直在折腾 ESP32 的多核功能,以及把任务固定到特定核心上。我用 AutoConnect 来支持 WiFi 连接,这样就不用把凭据写死在代码里,还能通过网页界面进行 OTA 更新。这是为一个项目准备的,它要控制一些 CPU 4 针 PWM 风扇,这些风扇就放在</description><pubDate>Wed, 29 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近我一直在折腾 ESP32 的多核功能,以及把任务固定到特定核心上。&lt;/p&gt;
&lt;p&gt;我用 &lt;a href=&quot;https://github.com/Hieromon/AutoConnect&quot;&gt;AutoConnect&lt;/a&gt; 来支持 WiFi 连接,这样就不用把凭据写死在代码里,还能通过网页界面进行 OTA 更新。&lt;/p&gt;
&lt;p&gt;这是为一个项目准备的:控制我服务器机柜里的几个 CPU 4 针 PWM 风扇,给里面的空气降温。为此我想把逻辑拆成两个任务。&lt;/p&gt;
&lt;p&gt;一个任务负责处理 API、传感器读取、更新、WiFi 等等,另一个任务负责中断和风扇的 PWM 控制。&lt;/p&gt;
&lt;p&gt;一切都很顺利,任务也创建好了,但运行代码时,如果让 ESP32 进入需要你先连上它、再告诉它去连你的 WiFi 网络的那种模式,它就开始出现奇怪的行为:每隔几秒就重启一次控制器,我怎么都没法完成连接。&lt;/p&gt;
&lt;p&gt;那段时间真是烦人,我不得不把控制器从原位拆下来,接上 USB 去刷新固件。&lt;/p&gt;
&lt;p&gt;折腾完之后我注意到,错误来自看门狗:&lt;/p&gt;
&lt;p&gt;Task watchdog got triggered. The following tasks did not feed the watchdog in time:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;IDLE (CPU 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Tasks currently running:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CPU 0: ManageTask&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CPU 1: ControllerTask&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;四处查了查,看起来这是由 ESP32 的任务看门狗(Task Watchdog)触发的,它用来确保各个任务都能轮到 CPU 执行。&lt;/p&gt;
&lt;p&gt;而且看起来,和直接跟其他所有代码、功能一起在任务之外运行相比,AutoConnect 在任务环境下运行时不太灵活。&lt;/p&gt;
&lt;p&gt;我挖了挖,试了各种办法,发现摆脱这种局面最简单的方法,就是直接禁用看门狗,然后祈祷一切都能及时执行 🙂 这个任务并不是关键任务,所以多等一会儿让它把手头的事做完也无妨。&lt;/p&gt;
&lt;p&gt;要禁用看门狗,你需要做两件事:&lt;/p&gt;
&lt;p&gt;第一,引入 &lt;code&gt;esp_task_wdt.h&lt;/code&gt; 头文件。为此,在你的 include 部分加上 &lt;code&gt;#include &amp;#x3C;esp_task_wdt.h&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;第二,让看门狗不再重置控制器,并且等待更长时间。为此,你需要在任务代码的开头加上 &lt;code&gt;esp_task_wdt_init(30, false);&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; manage_core_task&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;pv_parameters&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    esp_task_wdt_init&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // .... rest of the code ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会把看门狗的超时时间设为 30 &lt;strong&gt;秒&lt;/strong&gt;,并且在它被触发时不再重置控制器。&lt;/p&gt;
&lt;p&gt;如果你希望看门狗触发后仍然重置控制器,把函数调用的第二个参数从 &lt;code&gt;false&lt;/code&gt; 改成 &lt;code&gt;true&lt;/code&gt; 即可。&lt;/p&gt;</content:encoded><category>Uncategorized</category><category>arduino</category><category>code</category><category>esp32</category><category>espressif</category><category>multi-core</category><category>multi-threading</category></item><item><title>为 Git 提交签名设置 YubiKey</title><link>https://bisko.be/zh/posts/dev/setting-up-a-yubikey-for-git-commit-signing/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/setting-up-a-yubikey-for-git-commit-signing/</guid><description>我一直把 YubiKey 当作两步验证密钥，同时用它来给 Git 提交签名。几天前之前，我用的一直是 YubiKey 4。由于我现在的电脑没有任何 USB-A 接口，我的背包里总得带着 USB-A 转 USB-C 的转接头，过去一年里使用这把密钥一直很麻烦，对于</description><pubDate>Wed, 08 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我一直把 YubiKey 当作&lt;a href=&quot;https://en.wikipedia.org/wiki/Multi-factor_authentication&quot;&gt;两步验证&lt;/a&gt;密钥，同时用它来给 Git 提交签名。直到几天前，我用的一直是 YubiKey 4。由于我现在的电脑没有任何 USB-A 接口，我的背包里总得带着 USB-A 转 USB-C 的转接头，过去一年里使用这把密钥一直很麻烦。所以我决定是时候换成我那把“新”的 YubiKey 5C 了，这样就不用再折腾那些转接头了。&lt;/p&gt;
&lt;p&gt;其实这把密钥我早就有了，当时是因为 &lt;a href=&quot;https://www.yubico.com/2017/10/infineon-rsa-key-generation-issue/&quot;&gt;Infineon TPM&lt;/a&gt; 那个被他们发现的问题，我订了一把 YubiKey 4 作为替换，同时一起买了它，但直到现在我才决定换用新密钥。&lt;/p&gt;
&lt;p&gt;现在换的另一个原因是我的旧 GPG 密钥过期了，我不得不重新签发，所以正好可以用这把密钥来给我的 Git 提交签名。&lt;/p&gt;
&lt;h3 id=&quot;gpg-的疯狂&quot;&gt;GPG 的疯狂&lt;/h3&gt;
&lt;p&gt;如果你以前（或者现在）处理过 GPG 密钥，你就知道，对于从没接触过的人来说，想把它正确又安全地跑起来相当困难。主密钥要离线保存，公钥要来回倒腾，密钥过期后要重新签发，可能还要用旧密钥给新密钥签名以便验证，还要吊销密钥……哪怕只是在理论上遵循最佳实践都不容易，真要在实践中做到，事情就变得极其复杂了。&lt;/p&gt;
&lt;p&gt;网上大多数教程都是让你用命令行生成密钥，在设计模式很不常见的菜单里跳来跳去，想把它弄好简直是一种折磨。大多数教程还说要用离线机器（比如树莓派）和/或在离线状态下生成主密钥和子密钥，并且不要把主密钥放在你的电脑上。&lt;/p&gt;
&lt;p&gt;🤯&lt;/p&gt;
&lt;p&gt;过去将近 4 年里，每年一月初我都得面对这件事。每次看到下面这个错误，提示我的签名密钥已经过期，我都心头一紧。&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;error: gpg failed to sign the data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fatal: failed to write commit object&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今年我决定让这个过程变得简单得多，直接使用内置的证书生成功能，避开生成 GPG 密钥之类的所有麻烦。&lt;/p&gt;
&lt;h3 id=&quot;在-yubikey-上直接生成密钥&quot;&gt;在 YubiKey 上直接生成密钥&lt;/h3&gt;
&lt;p&gt;这种方式仍然需要使用命令行界面，但步骤更少，所以更直观一些。&lt;/p&gt;
&lt;p&gt;首先我们要设置好卡片/设备。&lt;/p&gt;
&lt;h3 id=&quot;卡片设备设置&quot;&gt;卡片/设备设置&lt;/h3&gt;
&lt;p&gt;在终端中运行 &lt;code&gt;gpg --card-status&lt;/code&gt;，你应该会看到类似下面的内容：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reader ………..: Yubico Yubikey 4 OTP U2F CCID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Application ID …: &amp;#x3C;Serial number&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Version ……….: 2.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Manufacturer …..: Yubico&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Serial number ….: &amp;#x3C;Device serial number&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Name of cardholder: unspecified&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Language prefs …: en&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sex …………..: unspecified&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;URL of public key : \[not set\]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Login data …….: &amp;#x3C;e-mail or nothing&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Signature PIN ….: not forced&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Key attributes …: rsa4096 rsa4096 rsa4096&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Max. PIN lengths .: 127 127 127&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;PIN retry counter : 3 0 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Signature counter : 9&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你看到类似的内容，并且没有出现错误，就可以继续了。&lt;/p&gt;
&lt;p&gt;运行 &lt;code&gt;gpg --card-edit&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;你会进入一个命令行界面，开头显示的是同样的信息，底部是命令提示符 &lt;code&gt;gpg/card&gt;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;输入 &lt;code&gt;admin&lt;/code&gt; 并按回车。&lt;/p&gt;
&lt;p&gt;现在你可以输入 &lt;code&gt;help&lt;/code&gt;，查看如何修改卡片的持有者信息和 PIN。&lt;/p&gt;
&lt;p&gt;有一件事你&lt;strong&gt;&lt;em&gt;必须&lt;/em&gt;&lt;/strong&gt;做，就是修改卡片的默认 PIN。一共有两个：第一个是用来访问卡上密钥的 PIN，第二个是“管理员”PIN，也叫 PUK（Personal Unblocking Code，个人解锁码）。&lt;/p&gt;
&lt;p&gt;要修改它们，输入 &lt;code&gt;passwd&lt;/code&gt;，你会看到一个菜单：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg/card&gt; passwd  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: OpenPGP card no. &amp;#x3C;Serial number&gt; detected&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 - change PIN  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2 - unblock PIN  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3 - change Admin PIN  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4 - set the Reset Code  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Q - quit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 &lt;code&gt;1&lt;/code&gt;，按提示更新你的 PIN。默认 PIN 是 &lt;code&gt;123456&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;完成后，选择 &lt;code&gt;3&lt;/code&gt; 来修改管理员 PIN。默认值是 &lt;code&gt;12345678&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;两个 PIN 都改完之后，回到主菜单，继续完善密钥信息。&lt;/p&gt;
&lt;p&gt;你可以使用的命令有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; – 修改持卡人姓名&lt;/li&gt;
&lt;li&gt;&lt;code&gt;login&lt;/code&gt; – 修改持卡人的电子邮件地址或登录名&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;之后我们就可以进入真正的密钥生成环节了。&lt;/p&gt;
&lt;h3 id=&quot;生成密钥&quot;&gt;生成密钥&lt;/h3&gt;
&lt;p&gt;现在是时候真正生成密钥了。&lt;/p&gt;
&lt;p&gt;输入 &lt;code&gt;generate&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;你会被问到 &lt;code&gt;Make off-card backup of encryption key? (Y/n)&lt;/code&gt;，不过正如 YubiKey 文档中提到的，这只是一个形式上的备份，并不会创建私钥的完整备份，所以回答 &lt;code&gt;Yes&lt;/code&gt; 还是 &lt;code&gt;No&lt;/code&gt; 都无所谓。我回答的是 &lt;code&gt;No(n)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果卡上已经存有密钥，系统会问你是否要替换它们：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: Note: keys are already stored on the card!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Replace existing keys? (y/N)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请确保你不会替换掉某些没有备份的重要东西，然后输入 &lt;code&gt;Y&lt;/code&gt; 继续。如果里面有重要的东西，最好用 &lt;code&gt;Ctrl+C&lt;/code&gt; 退出整个流程。&lt;/p&gt;
&lt;p&gt;系统会要求你输入之前设置的卡片 PIN。&lt;/p&gt;
&lt;p&gt;接着你需要选择密钥的有效期：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Please specify how long the key should be valid.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        0 = key does not expire&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;n&gt; = key expires in n days&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;#x3C;n&gt;w = key expires in n weeks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;#x3C;n&gt;m = key expires in n months&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &amp;#x3C;n&gt;y = key expires in n years&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Key is valid for? (0)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你希望密钥只有 1 年有效期，输入 &lt;code&gt;1y&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;系统会要求你确认到期日期。检查一切无误后确认。&lt;/p&gt;
&lt;p&gt;然后系统会要求你输入密钥所有者的信息：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Is this correct? (y/N) y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GnuPG needs to construct a user ID to identify your key.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Real name: &amp;#x3C;Your name&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Email address: &amp;#x3C;Your e-mail&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Comment: &amp;#x3C;Comment is optional&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; You selected this USER-ID:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &quot;&amp;#x3C;Your name&gt; &amp;#x3C;comment&gt; &amp;#x3C;your e-mail&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Type `O` to confirm if everything seems correct.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来会要求你输入管理员 PIN 或 PUK。这是必需的，因为它要对卡片执行写操作。&lt;/p&gt;
&lt;p&gt;然后如果一切正常，YubiKey 会开始闪烁，并且会持续闪一阵子（我这边大约要五分钟）。&lt;/p&gt;
&lt;p&gt;完成后你会看到下面的消息&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: key &amp;#x3C;Short ID&gt; marked as ultimately trusted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gpg: revocation certificate stored as &apos;/Users/bisko/.gnupg/openpgp-revocs.d/&amp;#x3C;LONG ID&gt;.rev&apos; public and secret key created and signed.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记下 &lt;code&gt;&amp;#x3C;Short ID&gt;&lt;/code&gt;，我们后面会用到。&lt;/p&gt;
&lt;p&gt;此时你可以用 &lt;code&gt;quit&lt;/code&gt; 或 &lt;code&gt;Ctrl+C&lt;/code&gt; 退出 GPG 提示符。&lt;/p&gt;
&lt;p&gt;现在该保存公钥了，这样我们就能在另一台电脑上复用这把密钥。&lt;/p&gt;
&lt;p&gt;为此，运行下面的命令：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --armor&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --export&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Short&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; I&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;D&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Short&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; I&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;D&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会把公钥导出到名为 &lt;code&gt;&amp;#x3C;Short ID&gt;.asc&lt;/code&gt; 的文件中。以后如果你想在另一台电脑上使用这把 YubiKey，或者丢失了 GPG 配置，就会用到它。&lt;/p&gt;
&lt;p&gt;要把公钥恢复到你的密钥环中，需要这样做：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --import&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Short&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; I&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;D&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.asc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Short&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; I&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;D&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; public&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Your Name &amp;#x3C;your@email&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; imported&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Total&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; processed:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;gpg:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; imported:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;这里有个非常重要的提示。&lt;/em&gt;&lt;/strong&gt;如果这是新安装的 GPG 或者是另一台电脑，GPG 不会自己识别卡上的密钥。你需要运行 &lt;code&gt;gpg --card-status&lt;/code&gt;，让它读取卡片并获取卡上的密钥。&lt;/p&gt;
&lt;h3 id=&quot;设置-git-签名&quot;&gt;设置 Git 签名&lt;/h3&gt;
&lt;p&gt;要让 Git 给你的提交签名，你需要在 &lt;code&gt;~/.gitconfig&lt;/code&gt; 中添加以下内容。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;[user]&lt;/code&gt; 部分下，添加（或更新）下面这一行：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;signingkey = 0x&amp;#x3C;Short ID&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后如果你想给所有提交都签名，在文件末尾添加以下内容：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;[gpg]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  program&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = /usr/local/bin/gpg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;[commit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  gpgsign&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; = true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会让 Git 给你所有的提交签名。&lt;/p&gt;
&lt;p&gt;要设置 GitHub，你需要把公钥添加到你的个人资料中。具体做法是进入 &lt;code&gt;GitHub -&gt; Settings -&gt; SSH and GPG keys -&gt; New GPG key&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然后你会看到一个文本框，把我们上面生成的 &lt;code&gt;&amp;#x3C;Short ID&gt;.asc&lt;/code&gt; 文件的全部内容粘贴进去。&lt;/p&gt;
&lt;p&gt;如果你想复制文件内容（在 macOS 上），可以这样：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cat &amp;#x3C;Short ID&gt;.asc | pbcopy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这会把整个文件的内容复制到剪贴板。&lt;/p&gt;
&lt;p&gt;把它粘贴到 GitHub 的文本框里，然后点击 &lt;code&gt;Add GPG key&lt;/code&gt; 按钮。&lt;/p&gt;
&lt;p&gt;之后你可以试着提交并推送到 GitHub，应该就能看到漂亮的 &lt;code&gt;Verified&lt;/code&gt; 标签了。&lt;/p&gt;
&lt;h3 id=&quot;调试&quot;&gt;调试&lt;/h3&gt;
&lt;p&gt;有时候事情不会一帆风顺。为了收集更多信息，你可以试试下面这些方法：&lt;/p&gt;
&lt;h3 id=&quot;用-gpg-签名&quot;&gt;用 GPG 签名&lt;/h3&gt;
&lt;p&gt;首先试着用这把密钥给某个东西签名：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gpg -s &amp;#x3C;file&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果失败了，试着弄清楚失败的原因，这方面我不是专家，帮不上忙。&lt;/p&gt;
&lt;p&gt;查看 Git 的输出GIT_TRACE=1 git commit -am “test”&lt;/p&gt;
&lt;p&gt;这会告诉你 Git 在尝试提交并签名时所做的每一步具体操作，能给你一些线索，看看签名为什么可能失败。&lt;/p&gt;
&lt;p&gt;从中你还可以看到，Git 执行了下面这条命令：&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/usr/local/bin/gpg&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --status-fd=2&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; -bsau&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Key&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; I&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;D&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你可以试着手动运行它，看看能不能得到更有用的错误信息。&lt;/p&gt;</content:encoded><category>Snippets</category><category>Technology</category><category>git</category><category>github</category><category>gpg</category><category>key</category><category>public-key</category><category>secrets</category><category>sign</category><category>yubikey</category></item><item><title>e1000e eth0: Detected Hardware Unit Hang（检测到硬件单元挂起）</title><link>https://bisko.be/zh/posts/dev/e1000e-device-id-eth0-detected-hardware-unit-hang/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/e1000e-device-id-eth0-detected-hardware-unit-hang/</guid><description>最近我的家用服务器兼虚拟机宿主机开始随机断网。从外面看，它似乎还在正常运行，可我却无法通过任何远程方式访问它。根据交换机的显示，以太网适配器好像是开着的，所以问题肯定出在软件层面。这不会是</description><pubDate>Wed, 18 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近我的家用服务器兼虚拟机宿主机开始随机断网。从外面看，它似乎还在正常运行，可我却无法通过任何远程方式访问它。根据交换机的显示，以太网适配器好像是开着的，所以问题肯定出在软件层面。&lt;/p&gt;
&lt;p&gt;驱动问题导致网络连接卡死，这可不是第一次了。过去我就被 Linux 和 Windows 电脑上有 bug 的 WiFi 驱动坑过。&lt;/p&gt;
&lt;p&gt;在系统日志里翻了一阵之后，我偶然发现了下面这些内容：&lt;/p&gt;
&lt;p&gt;vmhost kernel: e1000e 0000:00:1f.6 eth0: Detected Hardware Unit Hang:&lt;br&gt;
vmhost kernel:   TDH                  &amp;#x3C;0&gt;&lt;br&gt;
vmhost kernel:   TDT                  &amp;#x3C;1&gt;&lt;br&gt;
vmhost kernel:   next_to_use          &amp;#x3C;1&gt;&lt;br&gt;
vmhost kernel:   next_to_clean        &amp;#x3C;0&gt;&lt;br&gt;
vmhost kernel: buffer_info[next_to_clean]:&lt;br&gt;
vmhost kernel:   time_stamp           &amp;#x3C;10fbc2f81&gt;&lt;br&gt;
vmhost kernel:   next_to_watch        &amp;#x3C;0&gt;&lt;br&gt;
vmhost kernel:   jiffies              &amp;#x3C;10fbc3871&gt;&lt;br&gt;
vmhost kernel:   next_to_watch.status &amp;#x3C;0&gt;&lt;br&gt;
vmhost kernel: MAC Status             &amp;#x3C;40080083&gt;&lt;br&gt;
vmhost kernel: PHY Status             &amp;#x3C;796d&gt;&lt;br&gt;
vmhost kernel: PHY 1000BASE-T Status  &amp;#x3C;7800&gt;&lt;br&gt;
vmhost kernel: PHY Extended Status    &amp;#x3C;3000&gt;&lt;br&gt;
vmhost kernel: PCI Status             &amp;#x3C;10&gt;&lt;br&gt;
vmhost kernel: e1000e 0000:00:1f.6 eth0: Reset adapter unexpectedly&lt;br&gt;
vmhost kernel: vmbr0: port 1(eth0) entered disabled state&lt;/p&gt;
&lt;p&gt;然后它会重置网络适配器，过一会儿又来一遍，直到机器彻底掉线。&lt;/p&gt;
&lt;p&gt;我在网上找答案时，偶然发现了&lt;a href=&quot;https://serverfault.com/questions/616485/e1000e-reset-adapter-unexpectedly-detected-hardware-unit-hang&quot;&gt;这个 ServerFault 帖子&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;原因&lt;/p&gt;
&lt;p&gt;翻了不少资料和 bug 报告列表后，复现这个问题最好的办法似乎是让设备处于高带宽场景，也就是传输大量数据把网卡接口跑满。&lt;/p&gt;
&lt;p&gt;我的情况通常发生在从本地 Plex 服务器向某个设备串流媒体的时候。由于网络的搭建方式，运行 Plex 实例的 Windows 虚拟机必须先从另一台设备的 NFS 网络共享中取得媒体文件，在虚拟机里转码，再发送给播放设备。&lt;/p&gt;
&lt;p&gt;这样加起来网络流量相当大，通常在 ~40-100mbit/s 左右，取决于播放媒体文件的设备以及源媒体文件的质量。&lt;/p&gt;
&lt;p&gt;通过 Steam Link 向我们的 Apple TV 串流游戏时，也出现过同样的问题。连接是有线的，但网络掉线并不少见。我觉得这和上面是同一个问题，不过之后我会留意修复后是否还会发生。&lt;/p&gt;
&lt;p&gt;可能的修复方法&lt;/p&gt;
&lt;p&gt;看来一个可行的办法是在网络接口上关闭 &lt;strong&gt;GSO&lt;/strong&gt; &lt;em&gt;（Generic Segmentation Offload，通用分段卸载）&lt;/em&gt;、&lt;strong&gt;TSO&lt;/strong&gt; &lt;em&gt;（TCP Segmentation Offload，TCP 分段卸载）&lt;/em&gt; 和 &lt;strong&gt;GRO&lt;/strong&gt; &lt;em&gt;（Generic Receive Offload，通用接收卸载）&lt;/em&gt;*：&lt;/p&gt;
&lt;p&gt;ethtool -K eth0 gso off gro off tso off&lt;/p&gt;
&lt;p&gt;我已经在自己的环境里应用了这个设置，正在观察长期来看它是否真的能解决问题。&lt;/p&gt;
&lt;p&gt;脚注&lt;/p&gt;
&lt;p&gt;* 这些选项与把数据包分段工作卸载给网络接口控制器有关，目的是降低机器的 CPU 占用。更多细节可以参考&lt;a href=&quot;https://en.wikipedia.org/wiki/Large_send_offload&quot;&gt;这篇维基百科文章&lt;/a&gt;。&lt;/p&gt;</content:encoded><category>Homelab</category><category>bandwidth</category><category>bug</category><category>network</category><category>nic</category><category>plex</category><category>proxmox</category><category>steam</category><category>streaming</category></item><item><title>小技巧:运行本地/私有的 swarm Docker 镜像仓库</title><link>https://bisko.be/zh/posts/dev/snippets-running-a-local-private-swarm-docker-image-registry/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/snippets-running-a-local-private-swarm-docker-image-registry/</guid><description>今天我想从一个 Node.js 项目生成 Docker 镜像,然后把它部署到我本地的测试 swarm 上。不幸的是,如果在本地构建 Docker 镜像,这个镜像只会保存在创建它的那个节点上,只有该节点能访问,swarm 里的其他节点是访问不到的。</description><pubDate>Fri, 11 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今天我想从一个 Node.js 项目生成 Docker 镜像,然后把它部署到我本地的测试 swarm 上。&lt;/p&gt;
&lt;p&gt;不幸的是,如果在本地构建 Docker 镜像,这个镜像只会存储在创建它的那个节点上,只有该节点能访问,swarm 里的其他节点是访问不到的。&lt;/p&gt;
&lt;p&gt;解决办法是把镜像推送到 Docker 镜像仓库,这样 swarm 里所有节点都可以从那里拉取镜像。&lt;/p&gt;
&lt;p&gt;创建镜像仓库&lt;/p&gt;
&lt;p&gt;要在 swarm 中创建一个镜像仓库服务,运行:&lt;/p&gt;
&lt;p&gt;docker service create —name registry —publish 5000:5000 registry:latest&lt;/p&gt;
&lt;p&gt;这会创建 registry 服务并在 swarm 中运行它,swarm 里的所有节点都可以访问它。&lt;/p&gt;
&lt;p&gt;把镜像推送到镜像仓库&lt;/p&gt;
&lt;p&gt;要把镜像推送到镜像仓库,需要做两件事——先打标签,再推送镜像。&lt;/p&gt;
&lt;p&gt;下面是一个一步完成构建和打标签的例子:&lt;/p&gt;
&lt;p&gt;docker build . -t localhost.localdomain:5000/&lt;image-name&gt;:latest&lt;/image-name&gt;&lt;/p&gt;
&lt;p&gt;然后是如何把它推送到镜像仓库:&lt;/p&gt;
&lt;p&gt;docker push localhost.localdomain:5000/&lt;image-name&gt;:latest在服务/容器等中使用镜像&lt;/image-name&gt;&lt;/p&gt;
&lt;p&gt;要在服务或容器中使用这个镜像,需要指定上面的完整路径,即 &lt;code&gt;localhost.localdomain:5000/&amp;#x3C;image-name&gt;:latest&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;swarm 节点将能够访问该镜像,并拉取和运行它。&lt;/p&gt;
&lt;p&gt;关于仓库地址的重要说明&lt;/p&gt;
&lt;p&gt;网上大多数教程里,仓库地址写的都是 &lt;code&gt;localhost:5000&lt;/code&gt;。不幸的是,这样行不通,使用时通常会超时。如果把它改成 &lt;code&gt;localhost.localdomain&lt;/code&gt;,就能正常工作了!&lt;/p&gt;
&lt;p&gt;我也不清楚为什么会这样,明明 &lt;code&gt;localhost&lt;/code&gt; 和 &lt;code&gt;localhost.localdomain&lt;/code&gt; 都解析到 &lt;code&gt;127.0.0.1&lt;/code&gt;。&lt;/p&gt;</content:encoded><category>Homelab</category><category>Snippets</category><category>Technology</category><category>docker</category><category>docker-swarm</category><category>registry</category><category>swarm</category></item><item><title>小技巧:把 Apple 键盘上的分节符键映射为反引号键(§ 改成 `)</title><link>https://bisko.be/zh/posts/dev/snippets-map-apple-keyboard-section-sign-key-to-back-quote-%C2%A7-to/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/snippets-map-apple-keyboard-section-sign-key-to-back-quote-%C2%A7-to/</guid><description>我用 Apple 键盘已经有一阵子了,但住在欧洲的话,想买到我用惯了的美式布局键盘可不容易。在 macOS 上有一个“简单”的办法,只需一条终端命令就能重新映射键盘:watch -n1 -x hidutil property -m ‘{“ProductID”:592, “VendorID”:1452}’ –set ‘{“UserKeyMapping”:[{“HIDKeyboardModifierMappingSrc”:0x700000035,”HIDKeyboardModifierMappingDst”:0x700000064}]}’ 这段代码的作用是</description><pubDate>Wed, 31 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我用 Apple 键盘已经有一阵子了,但住在欧洲的话,想买到我用惯了的美式布局键盘可不容易。&lt;/p&gt;
&lt;p&gt;在 macOS 上有一个“简单”的办法,只需一条终端命令就能重新映射键盘:&lt;/p&gt;
&lt;p&gt;watch -n1 -x hidutil property -m ’{“ProductID”:592, “VendorID”:1452}’ —set ’{“UserKeyMapping”:[{“HIDKeyboardModifierMappingSrc”:0x700000035,“HIDKeyboardModifierMappingDst”:0x700000064}]}’&lt;/p&gt;
&lt;p&gt;这段代码的作用,是把分节符键( § )改成输出反引号/波浪号( &lt;code&gt;`/ ~&lt;/code&gt; )键。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;watch -n1 -x&lt;/code&gt; 这部分会每秒执行一次,因为 macOS 有时会把这个设置弄丢,通常发生在电脑进入睡眠,或者键盘被拔掉再重新插上的时候。&lt;/p&gt;
&lt;p&gt;请注意,在某些版本的 macOS 上并没有预装 &lt;code&gt;watch&lt;/code&gt; 工具,需要通过 &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; 或 &lt;a href=&quot;https://www.macports.org/&quot;&gt;MacPorts&lt;/a&gt; 之类的工具来安装。&lt;/p&gt;
&lt;p&gt;上面的代码适用于 USB 接口、带保加利亚语字母的欧洲布局 Apple 外接键盘。对于其他类型的键盘,可能需要对按键的十六进制值稍作调整。&lt;/p&gt;
&lt;p&gt;这条命令的一个变体,同样适用于 MacBook Pro 自带的同类键盘,只需要把 &lt;code&gt;0x700000035&lt;/code&gt; 和 &lt;code&gt;0x700000064&lt;/code&gt; 这两个值的位置对调即可。&lt;/p&gt;
&lt;p&gt;我一直在想要不要写个工具来自动帮我做这件事,不过现在我觉得,还是花钱买一把布局正确的新键盘更划算 🙂&lt;/p&gt;</content:encoded><category>Uncategorized</category></item><item><title>重置/重启 Apple TV 遥控器</title><link>https://bisko.be/zh/posts/dev/resetting-restarting-apple-tv-remote/</link><guid isPermaLink="true">https://bisko.be/zh/posts/dev/resetting-restarting-apple-tv-remote/</guid><description>昨晚家里来客人的时候，我居然把 Apple TV 遥控器泡进了水里。睡眠不足加上眼镜没戴稳，事故就这么发生了。昨晚遥控器还好好的，今天却罢工了，怎么按都没反应。我试着往手心里猛拍，又使劲把水甩出来。</description><pubDate>Sun, 17 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;昨晚家里来客人的时候，我居然把 Apple TV 遥控器泡进了水里。睡眠不足加上眼镜没戴稳，事故就这么发生了。&lt;/p&gt;
&lt;p&gt;昨晚遥控器还好好的，今天却罢工了，怎么按都没反应。我试着往手心里猛拍，又使劲把里面的水甩出来。完全没用。&lt;/p&gt;
&lt;p&gt;我们都以为它彻底报废了，开始在橱柜里翻找米袋。&lt;/p&gt;
&lt;p&gt;我上网搜了一下，其实主要是抱着开玩笑的心态，想看看能不能“重启”它。&lt;/p&gt;
&lt;p&gt;一篇很有用的指南让我同时按住音量加（Volume Up）键和菜单（Menu）键 2-3 秒，就能重置遥控器并进入配对模式。&lt;/p&gt;
&lt;p&gt;结果我真的大吃一惊，&lt;strong&gt;它居然真的好了&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;所以，如果遥控器有点不对劲，先试试重置吧。如果不行，再去考虑埋进米里和拆机维修也不迟。&lt;/p&gt;
&lt;p&gt;简而言之：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按住 Menu + Volume Up 2-3 秒&lt;/li&gt;
&lt;li&gt;等几秒钟&lt;/li&gt;
&lt;li&gt;试着操作一下 Apple TV&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>Apple</category><category>Technology</category><category>AppleTV</category><category>Have you tried turning it off and on again?</category><category>remote</category><category>reset</category><category>restart</category></item></channel></rss>