<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Koala</title><link>https://blog.therainisme.com/zh/</link><description>Recent content on Koala</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Sat, 18 Apr 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.therainisme.com/zh/index.xml" rel="self" type="application/rss+xml"/><item><title>Veil：让容器流量自动走代理</title><link>https://blog.therainisme.com/zh/posts/2026-04-18-placeholder/</link><pubDate>Sat, 18 Apr 2026 00:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/posts/2026-04-18-placeholder/</guid><description>&lt;p&gt;之前在服务器上跑容器，有些容器需要走代理才能联网。一开始我每个容器里都装代理客户端，配环境变量、映射端口，几台机器下来就烦了。后来一想，与其每个容器单独折腾，不如直接建一个共享的代理网络，容器加进来就自动走代理，不用改任何配置。项目开源在 &lt;a href="https://github.com/therainisme/veil"&gt;github.com/therainisme/veil&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="怎么做到的"&gt;怎么做到的&lt;/h2&gt;
&lt;p&gt;两个容器：&lt;code&gt;veil-net&lt;/code&gt; 跑 Mihomo（开了 TUN 模式），&lt;code&gt;veil-box&lt;/code&gt; 是干活的环境。关键就一行配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fb4934"&gt;network_mode&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;service:veil-net&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这让 &lt;code&gt;veil-box&lt;/code&gt; 共享 &lt;code&gt;veil-net&lt;/code&gt; 的网络栈，所有流量都经过 Mihomo 的 TUN 虚拟网卡，DNS 也被 Mihomo 劫持到 &lt;code&gt;127.0.0.1:53&lt;/code&gt; 处理。容器里的程序完全不知道代理的存在，该联网联网，该解析域名解析域名。&lt;/p&gt;
&lt;p&gt;选 TUN 主要是图省事。HTTP/SOCKS 代理得每个应用手动配，很多命令行工具还不支持；Redir 模式只能代理 TCP，DNS 查询这种 UDP 流量管不了。TUN 在内核层面截获所有流量，TCP、UDP、ICMP 全都拿下，真正意义上透明。&lt;/p&gt;
&lt;h2 id="用法"&gt;用法&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp veil.yml.example veil.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#928374;font-style:italic"&gt;# 编辑 veil.yml，填上你的代理服务器信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vim veil.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker &lt;span style="color:#fabd2f"&gt;exec&lt;/span&gt; -it -u veil veil-box bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;进容器后直接 &lt;code&gt;curl google.com&lt;/code&gt; 试试，不需要设 &lt;code&gt;http_proxy&lt;/code&gt;，也不需要改任何应用配置。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;veil.yml.example&lt;/code&gt; 模板已经在仓库里了，DNS 配置和分流规则都写好了，开箱即用。&lt;/p&gt;
&lt;h2 id="两个版本"&gt;两个版本&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;veil-box&lt;/strong&gt;：日常开发环境，装了 git、vim、python3、build-essential 这些常用工具&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;veil-actions-runner&lt;/strong&gt;：在 veil-box 基础上加了 Docker CE，用来跑需要代理的 CI/CD 流水线&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对应的 compose 文件分别是 &lt;code&gt;docker-compose.yml&lt;/code&gt; 和 &lt;code&gt;docker-compose.yml.actions-runner&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>修改 WSL2 的默认内存限制</title><link>https://blog.therainisme.com/zh/posts/wsl2-memory-limit/</link><pubDate>Fri, 26 Dec 2025 12:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/posts/wsl2-memory-limit/</guid><description>&lt;p&gt;最近在 WSL2 里跑一个 Go 程序，内存用到 12GB 的时候就不动了（本机内存 24GB）。命令行里啥也没有，既没报错也没 panic，就这么静悄悄地停了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;~ Run go &lt;span style="color:#fabd2f"&gt;test&lt;/span&gt; ./
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;===&lt;/span&gt; RUN TestDBMetaphysicsProblem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;--- PASS: TestDBMetaphysicsProblem &lt;span style="color:#fe8019"&gt;(&lt;/span&gt;10.94s&lt;span style="color:#fe8019"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;===&lt;/span&gt; RUN TestDBSequentialOP
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FAIL	github.com/therainisme/mlsm	182.824s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一查文档发现，WSL2 默认内存大小上限是 Windows 的一半，来源：https://learn.microsoft.com/en-us/windows/wsl/wsl-config#main-wsl-settings&lt;/p&gt;
&lt;h2 id="配置内存限制"&gt;配置内存限制&lt;/h2&gt;
&lt;p&gt;在用户文件夹下（路径一般是 &lt;code&gt;C:\users\你的用户名&lt;/code&gt;）新建一个 &lt;code&gt;.wslconfig&lt;/code&gt; 文件，写入以下内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;[wsl2]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#b8bb26;font-weight:bold"&gt;memory&lt;/span&gt;&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;16GB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;保存后用 &lt;code&gt;wsl --shutdown&lt;/code&gt; 重启 WSL2。接着在 WSL2 里跑一下 &lt;code&gt;free&lt;/code&gt; 命令，即可验证配置有没有生效：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;~ free
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; total used free shared buff/cache available
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Mem: &lt;span style="color:#d3869b"&gt;16381032&lt;/span&gt; &lt;span style="color:#d3869b"&gt;909836&lt;/span&gt; &lt;span style="color:#d3869b"&gt;14426152&lt;/span&gt; &lt;span style="color:#d3869b"&gt;980&lt;/span&gt; &lt;span style="color:#d3869b"&gt;1045044&lt;/span&gt; &lt;span style="color:#d3869b"&gt;15175252&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Swap: &lt;span style="color:#d3869b"&gt;4194304&lt;/span&gt; &lt;span style="color:#d3869b"&gt;87336&lt;/span&gt; &lt;span style="color:#d3869b"&gt;4106968&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置好之后可以看到，内存变成 16GB 了，单位是字节。占大内存的 Go 程序终于能正常跑完了。&lt;/p&gt;</description></item><item><title>Git 多人协作开发常见问题与解决方案</title><link>https://blog.therainisme.com/zh/posts/git-common-commands/</link><pubDate>Thu, 18 Dec 2025 21:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/posts/git-common-commands/</guid><description>&lt;p&gt;多人协作开发时，Git 使用不当很容易踩坑：冲突解决不了、误删别人代码、提交历史乱成一团……这篇文章整理了团队开发中最常遇到的问题和实战解决方案。&lt;/p&gt;
&lt;h2 id="场景一代码冲突了怎么办"&gt;场景一：代码冲突了怎么办？&lt;/h2&gt;
&lt;p&gt;多人同时修改同一个文件，&lt;code&gt;git pull&lt;/code&gt; 或 &lt;code&gt;git merge&lt;/code&gt; 时就会遇到冲突。这是团队开发中最常见的问题。&lt;/p&gt;
&lt;h3 id="情况-1pull-时发生冲突"&gt;情况 1：Pull 时发生冲突&lt;/h3&gt;
&lt;p&gt;当你执行 &lt;code&gt;git pull&lt;/code&gt; 时提示冲突：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git pull
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Auto-merging src/utils.js
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CONFLICT &lt;span style="color:#fe8019"&gt;(&lt;/span&gt;content&lt;span style="color:#fe8019"&gt;)&lt;/span&gt;: Merge conflict in src/utils.js
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Automatic merge failed; fix conflicts and &lt;span style="color:#fe8019"&gt;then&lt;/span&gt; commit the result.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;解决步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;查看冲突文件&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;标记为 &lt;code&gt;both modified&lt;/code&gt; 的文件就是冲突文件。&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;&lt;strong&gt;打开冲突文件，手动解决冲突&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;冲突部分会被标记出来：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; HEAD
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;function&lt;/span&gt; calculate(a, b) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; a &lt;span style="color:#fe8019"&gt;+&lt;/span&gt; b;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;=======&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;function&lt;/span&gt; calculate(x, y) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; x &lt;span style="color:#fe8019"&gt;*&lt;/span&gt; y;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; origin&lt;span style="color:#fe8019"&gt;/&lt;/span&gt;main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD&lt;/code&gt; 到 &lt;code&gt;=======&lt;/code&gt; 之间是你的本地修改&lt;/li&gt;
&lt;li&gt;&lt;code&gt;=======&lt;/code&gt; 到 &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; origin/main&lt;/code&gt; 之间是远程的修改&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;手动编辑，保留需要的代码，删除标记符号。&lt;/p&gt;</description></item><item><title>Tailscale Peer Relay 让内网穿透更加稳定！</title><link>https://blog.therainisme.com/zh/posts/tailscale-peer-relay/</link><pubDate>Thu, 18 Dec 2025 14:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/posts/tailscale-peer-relay/</guid><description>&lt;p&gt;在 Tailscale 网络中，当设备之间无法建立 P2P 直连时，一般需要依赖 DERP（Designated Encrypted Relay for Packets）服务器来中继流量。而 2025 年 10 月，Tailscale 推出了全新的 &lt;strong&gt;Peer Relay&lt;/strong&gt; 功能，让内网穿透更加简单而稳定。&lt;/p&gt;
&lt;p&gt;Peer Relay 允许你将 Tailnet 网络中的任意设备指定为中继节点，用于转发其他设备之间的加密流量。当两台设备无法直连时，Tailscale 会优先尝试使用 Peer Relay，只有在 Relay 不可用时才回退到官方 DERP 服务器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;省流：Peer Relay 就是把你自己网络中的某台机器变成专属中继站&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="peer-relay-vs-derp"&gt;Peer Relay vs DERP&lt;/h2&gt;
&lt;h3 id="derp-的特点"&gt;DERP 的特点&lt;/h3&gt;
&lt;p&gt;DERP 服务器由官方维护，拥有全球节点覆盖，可以自动选择最近的节点，使用时无需额外配置。但自建 DERP 时需要域名和 SSL 证书，国内还需要备案，配置相对复杂。自建时还需要考虑万人骑问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;因为特殊原因，Tailscale 官方没有国内的 DERP 服务器，所以使用体验也不佳。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="peer-relay-的特点"&gt;Peer Relay 的特点&lt;/h3&gt;
&lt;p&gt;Peer Relay 无需域名、证书和备案，免费用户也可以配置 2 个中继节点。&lt;/p&gt;
&lt;h2 id="前提条件"&gt;前提条件&lt;/h2&gt;
&lt;p&gt;在开始配置之前，请确保：(1) 所有设备运行 &lt;strong&gt;Tailscale 1.86&lt;/strong&gt; 或更高版本。（2）至少有一台服务器能够被其他节点访问。&lt;/p&gt;
&lt;h2 id="配置教程"&gt;配置教程&lt;/h2&gt;
&lt;h3 id="第一步准备公网服务器"&gt;第一步：准备公网服务器&lt;/h3&gt;
&lt;p&gt;需要一台具有公网 IP 的服务器作为中继节点。这台服务器可以是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;云服务商的 VPS（阿里云、腾讯云等）&lt;/li&gt;
&lt;li&gt;有公网 IP 的家庭宽带设备&lt;/li&gt;
&lt;li&gt;Full Cone NAT 环境下的设备&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="第二步在中继服务器上启用-peer-relay"&gt;第二步：在中继服务器上启用 Peer Relay&lt;/h3&gt;
&lt;p&gt;在作为中继的服务器上执行以下命令：&lt;/p&gt;</description></item><item><title>在 Linux 上配置 Docker 代理</title><link>https://blog.therainisme.com/zh/posts/docker-proxy/</link><pubDate>Thu, 18 Dec 2025 12:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/posts/docker-proxy/</guid><description>&lt;p&gt;开发或部署时，Docker 容器镜像往往需要从海外仓库拉取，网络不佳就会卡在 &lt;code&gt;docker pull&lt;/code&gt;。最稳妥的办法是在宿主机为 Docker Daemon 配置一个稳定的 HTTP 代理，这样所有镜像拉取都会自动走代理通道。&lt;/p&gt;
&lt;h2 id="配置-daemonjson"&gt;配置 daemon.json&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo mkdir -p /etc/docker
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo nano /etc/docker/daemon.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果文件不存在，可以先创建一个空文件再写入以下内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;&amp;#34;proxies&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;&amp;#34;http-proxy&amp;#34;&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;http://&amp;lt;host&amp;gt;:&amp;lt;port&amp;gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;&amp;#34;https-proxy&amp;#34;&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;http://&amp;lt;host&amp;gt;:&amp;lt;port&amp;gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fb4934"&gt;&amp;#34;no-proxy&amp;#34;&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;localhost,127.0.0.1,::1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http-proxy&lt;/code&gt;/&lt;code&gt;https-proxy&lt;/code&gt;：指向网络中可用的代理服务。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;no-proxy&lt;/code&gt;：列出访问这些地址时不经过代理，避免本地容器互访走外网绕远。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;保存后执行以下命令让配置立刻生效：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo systemctl daemon-reload
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo systemctl restart docker
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="验证代理是否生效"&gt;验证代理是否生效&lt;/h2&gt;
&lt;p&gt;运行 &lt;code&gt;docker info | grep -i proxy&lt;/code&gt;，应能看到 HTTP/HTTPS Proxy 字段。&lt;/p&gt;
&lt;h2 id="常见问题"&gt;常见问题&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代理变更&lt;/strong&gt;：更新 &lt;code&gt;daemon.json&lt;/code&gt;，重新执行上述两行 systemctl 的命令。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;私有仓库不想走代理&lt;/strong&gt;：把仓库域名加进 &lt;code&gt;no-proxy&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Hugo 部署到 Vercel 只显示 index.xml</title><link>https://blog.therainisme.com/zh/posts/hugo-vercel-index-xml-issue/</link><pubDate>Tue, 16 Dec 2025 20:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/posts/hugo-vercel-index-xml-issue/</guid><description>&lt;p&gt;把 Hugo 博客部署到 Vercel，结果访问网站只显示 &lt;code&gt;index.xml&lt;/code&gt; 的内容。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.therainisme.com/zh/posts/hugo-vercel-index-xml-issue/vercel-display-xml.png" alt=""&gt;&lt;/p&gt;
&lt;h2 id="问题现象"&gt;问题现象&lt;/h2&gt;
&lt;p&gt;部署日志里一堆警告，没有报错：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;18:17:06.313 Installing Hugo version 0.58.2
18:17:24.069 Building sites … 
WARN 2025/12/16 10:17:24 found no layout file for &amp;#34;HTML&amp;#34; for &amp;#34;home&amp;#34;
WARN 2025/12/16 10:17:24 found no layout file for &amp;#34;HTML&amp;#34; for &amp;#34;section&amp;#34;
WARN 2025/12/16 10:17:24 found no layout file for &amp;#34;HTML&amp;#34; for &amp;#34;page&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;构建统计显示：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; Pages | 6 
 Paginator pages | 0 
 Non-page files | 0 
 Static files | 2 
 Processed images | 0 
 Aliases | 0 
 Sitemaps | 1 
 Cleaned | 0 

Total in 12 ms
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Static files 只有 2 个，但构建只花了 12 毫秒，一堆找不到布局文件的警告。根本就是没有正常 build 嘛，导致访问网站就只有 RSS 订阅源。&lt;/p&gt;</description></item><item><title>白嫖 Vercel 搭建 GitHub 加速代理</title><link>https://blog.therainisme.com/zh/posts/vercel-github/</link><pubDate>Sat, 13 Dec 2025 08:45:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/posts/vercel-github/</guid><description>&lt;p&gt;搞这个代理服务的初衷，其实很朴实：我需要一个能加速 &lt;code&gt;git clone&lt;/code&gt; 的小工具，但又不想多掏一分钱去维护一台服务器。Vercel 的免费套餐在性能和流量上都非常慷慨，简直就是想白嫖资本家羊毛的最好选择。&lt;/p&gt;
&lt;p&gt;我的需要部署一个轻量级服务，实现以下功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将所有请求转发到 &lt;code&gt;https://github.com&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;确保能处理大文件的 &lt;code&gt;git clone&lt;/code&gt; 和 &lt;code&gt;push&lt;/code&gt;，不能有限制。&lt;/li&gt;
&lt;li&gt;访问主页 &lt;code&gt;/&lt;/code&gt; 时，返回一段简单明了的 Usage 文本。&lt;/li&gt;
&lt;li&gt;设置 &lt;code&gt;/robots.txt&lt;/code&gt; 阻止搜索引擎收录。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;参考了 &lt;a href="https://yuuza.net/"&gt;@lideming&lt;/a&gt; 的 &lt;a href="https://md.yuuza.net/s/U52dzJZWd"&gt;https://md.yuuza.net/s/U52dzJZWd&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="为什么是-typescript--edge"&gt;为什么是 TypeScript + Edge？&lt;/h2&gt;
&lt;p&gt;一开始我根本没打算用 TypeScript。作为一个熟悉后端的人，第一反应是使用 Go 语言，因为 Go 标准库里的 &lt;code&gt;ReverseProxy&lt;/code&gt; 作为反向代理简单高效。&lt;/p&gt;
&lt;p&gt;我迅速用 Go 实现了第一版代码并部署到了 Vercel。在小流量测试中，一切正常。&lt;/p&gt;
&lt;p&gt;但只要一尝试 &lt;code&gt;git clone&lt;/code&gt; 稍微大一点的仓库，Vercel 云端 Log 会报 &lt;code&gt;413 Payload Too Large&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="go-的失败是-serverless-架构的限制"&gt;Go 的失败，是 Serverless 架构的限制&lt;/h3&gt;
&lt;p&gt;这个问题不在于 Go 代码本身，而在于它运行的&lt;strong&gt;环境&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Vercel 的标准 Serverless Function（Go 就运行在这个层级上）底层基于 AWS Lambda 等技术，它在处理请求时，为了安全和稳定性，会&lt;strong&gt;缓冲 (Buffer) 整个请求体&lt;/strong&gt;。当你要传输一个几 MB 甚至更大的 Git 数据包时，数据会在进入你的 Go 代码之前，就被&lt;a href="https://vercel.com/docs/errors/FUNCTION_PAYLOAD_TOO_LARGE"&gt;Vercel 设置的 4.5MB 限制卡着&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;，然后返回 413 错误。&lt;/p&gt;</description></item><item><title>NixOS 春节假期折腾之旅</title><link>https://blog.therainisme.com/zh/posts/nixos-spring-festival-journey/</link><pubDate>Mon, 10 Feb 2025 00:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/posts/nixos-spring-festival-journey/</guid><description>&lt;p&gt;春节在家刷到 &lt;a href="https://thiscute.world/posts/my-experience-of-nixos/#nixos-future"&gt;ryan4yin&lt;/a&gt; 的博客，看完觉得 NixOS 挺有意思。折腾 Linux 这么久，/etc 下的配置文件改吐了，依赖冲突也踩了不少坑。系统一升级环境就炸，Docker 也只能临时救个急。&lt;/p&gt;
&lt;h2 id="nixos-让人强烈欲望入坑的特点"&gt;NixOS 让人强烈欲望入坑的特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;用一份 &lt;code&gt;/etc/nixos/configuration.nix&lt;/code&gt; 文件定义整个系统&lt;/li&gt;
&lt;li&gt;每次系统配置更新，都会提供 Generation 防止错误配置无法回退&lt;/li&gt;
&lt;li&gt;完全可复现的开发环境，只需要 &lt;code&gt;git clone&lt;/code&gt;，不再需要 &lt;code&gt;pacman&lt;/code&gt; 或 &lt;code&gt;apt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="可复现特性基础"&gt;可复现特性基础&lt;/h3&gt;
&lt;p&gt;NixOS 中 &lt;code&gt;/nix/store&lt;/code&gt; 目录是只读的，这个目录存储了所有通过 Nix 包管理器安装的软件包、库文件和配置文件。每个包都有一个唯一的哈希值路径，例如 &lt;code&gt;/nix/store/l9i65cfgnxgrxlghxg792146w00kafcn-rocksdb-9.8.4/&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;只读设计保证了软件包不会被乱改，同一个包的不同版本可以共存，系统更新出问题了也能直接回滚。这就是 NixOS 能做到完全可复现的关键。&lt;/p&gt;
&lt;h2 id="安装-nixos"&gt;安装 NixOS&lt;/h2&gt;
&lt;p&gt;我们可以从 NixOS 官方下载 &lt;a href="https://nixos.org/download/#nix-install-linux"&gt;Minimal ISO image&lt;/a&gt;，在一台全新的机器上安装 NixOS。或是通过 &lt;a href="https://github.com/elitak/nixos-infect"&gt;nixos-infect&lt;/a&gt; 脚本，一键将非 NixOS 的系统转换成 NixOS。后者在阿里云 ECS 上测试是可用的。&lt;/p&gt;
&lt;p&gt;非常非常 Easy 的配置文件 &lt;code&gt;/etc/nixos/configuration.nix&lt;/code&gt;，在下面的配置中，我们指定了 root 用户的 authorizedKeys 以及密码，配置了 ssh-server，配置了 vscode-server，主机名等信息都可以通过该配置文件完成。当把下面的文件写入后，只需要运行 &lt;code&gt;nixos-rebuild switch&lt;/code&gt;，一切都完成了。&lt;/p&gt;
&lt;p&gt;我的服务器通过手动划分磁盘，挂载 btrfs 后，执行 &lt;code&gt;nixos-install&lt;/code&gt; 脚本安装。更详细的安装教程可以参考这篇博客：&lt;a href="https://dev.leiyanhui.com/nixos/start/"&gt;从 0 实现全集梦中情 OS&lt;/a&gt;。如果对于操作没有那么熟悉，可以现在虚拟机中尝试，后续想部署到服务器上，只需要复制配置文件到服务器，执行 &lt;code&gt;nixos-rebuild switch&lt;/code&gt;，就都一模一样了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#928374;font-style:italic"&gt;# Edit this configuration file to define what should be installed on&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#928374;font-style:italic"&gt;# your system. Help is available in the configuration.nix(5) man page, on&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#928374;font-style:italic"&gt;# https://search.nixos.org/options and in the NixOS manual (`nixos-help`).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{ config&lt;span style="color:#fe8019"&gt;,&lt;/span&gt; lib&lt;span style="color:#fe8019"&gt;,&lt;/span&gt; pkgs&lt;span style="color:#fe8019"&gt;,&lt;/span&gt; &lt;span style="color:#fe8019"&gt;...&lt;/span&gt; }:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; imports &lt;span style="color:#fe8019"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [ &lt;span style="color:#928374;font-style:italic"&gt;# Include the results of the hardware scan.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b8bb26"&gt;./hardware-configuration.nix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# vscode server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; (&lt;span style="color:#fabd2f"&gt;fetchTarball&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;https://github.com/nix-community/nixos-vscode-server/tarball/master&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sha256 &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;09j4kvsxw1d5dvnhbsgih0icbrxqv90nzf0b589rb5z6gnzwjnqf&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; boot&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;loader&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;systemd-boot&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;enable &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; boot&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;loader&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;efi&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;canTouchEfiVariables &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# Pick only one of the below networking options.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# networking.wireless.enable = true;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networking&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;networkmanager&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;enable &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# 配置时区等机器信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; time&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;timeZone &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;Asia/Shanghai&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; i18n&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;defaultLocale &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;en_US.UTF-8&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networking&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;hostName &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;hostname&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nixpkgs&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;config&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;allowUnfree &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# 安装的软件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;systemPackages &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#fe8019"&gt;with&lt;/span&gt; pkgs; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wget
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; curl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nano
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tailscale
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# 设置默认编辑器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;variables &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EDITOR &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;nano&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# 配置 OpenSSH 和 Tailscale&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; services&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;openssh&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;settings &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PermitRootLogin &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;yes&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PasswordAuthentication &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# 启动系统服务，类似 systemctl enable --now xxx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; services&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;openssh&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;enable&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#d3869b"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; services&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;tailscale&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;enable &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; services&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;vscode-server&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;enable &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# Root 用户的配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; users&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;users&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;root &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssh&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;authorizedKeys&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;keys &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;ssh-rsa AAAAB3NzaC......tTMFP5y8v8= therainisme@qq.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#928374;font-style:italic"&gt;# 这密码我随便写的&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; password &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; system&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;stateVersion &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;24.11&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="开发环境配置devbox-与-direnv"&gt;开发环境配置（Devbox 与 Direnv）&lt;/h2&gt;
&lt;p&gt;在软件开发中，环境配置一直是令人头疼的难题。不同项目对编译器版本、依赖库、环境变量的差异化需求，常常导致&amp;quot;在我的机器上能运行&amp;quot;的经典问题。接下来将介绍基于 Devbox 和 Direnv 管理环境方案，通过两个典型 Go 项目案例，展示如何实现开发环境的精准控制。&lt;/p&gt;</description></item><item><title/><link>https://blog.therainisme.com/zh/moments/</link><pubDate>Wed, 01 Jan 2025 00:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/moments/</guid><description>&lt;style&gt;
/* 瀑布流容器样式 - 使用 Flexbox 布局 */
.waterfall-container {
 display: flex;
 gap: 16px;
 max-width: 1200px;
 margin: 0 auto;
 align-items: flex-start;
}

/* 列容器 */
.waterfall-column {
 flex: 1;
 display: flex;
 flex-direction: column;
 gap: 16px;
}

/* 瀑布流项目样式 */
.waterfall-item {
 cursor: pointer;
 position: relative;
 overflow: hidden;
 border-radius: 12px;
 background: #f5f5f5;
 transition: transform 0.3s ease, box-shadow 0.3s ease;
 width: 100%;
 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
 user-select: none;
 -webkit-user-select: none;
 -moz-user-select: none;
 -ms-user-select: none;
}

/* 横屏图片：统一高度 */
.waterfall-item.horizontal {
 height: 200px;
}

/* 竖屏图片：统一高度 */
.waterfall-item.vertical {
 height: 300px;
}

.waterfall-item:hover {
 transform: translateY(-8px);
 box-shadow: 0 12px 28px rgba(0, 0, 0, 0.2);
 z-index: 10;
}

.waterfall-item img {
 width: 100%;
 height: 100%;
 display: block;
 margin: 0;
 border-radius: 12px;
 object-fit: cover;
 transition: transform 0.3s ease, filter 0.3s ease;
}

.waterfall-item:hover img {
 transform: scale(1.02);
}

/* 照片信息卡片 */
.photo-info {
 position: absolute;
 bottom: 0;
 left: 0;
 right: 0;
 padding: 10px 12px;
 background: linear-gradient(to top, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.3) 50%, transparent 100%);
 color: white;
 transform: translateY(100%);
 transition: transform 0.3s ease;
 border-radius: 0 0 12px 12px;
}

.waterfall-item:hover .photo-info {
 transform: translateY(0);
}

.photo-date {
 font-size: 11px;
 opacity: 0.95;
 margin-bottom: 3px;
 line-height: 1.2;
 text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8),
 0 0 5px rgba(0, 0, 0, 0.6);
}

.photo-location {
 font-size: 13px;
 font-weight: 600;
 margin-bottom: 4px;
 line-height: 1.3;
 text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8),
 0 0 5px rgba(0, 0, 0, 0.6);
}

.photo-thought {
 font-size: 12px;
 line-height: 1.4;
 opacity: 0.95;
 font-style: italic;
 display: -webkit-box;
 -webkit-line-clamp: 2;
 -webkit-box-orient: vertical;
 overflow: hidden;
 text-overflow: ellipsis;
 text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8),
 0 0 5px rgba(0, 0, 0, 0.6);
}

/* 暗色模式支持 */
.dark .waterfall-item {
 background: #2a2a2a;
}

.dark .waterfall-item:hover {
 box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
}

/* 响应式布局 */
@media (max-width: 768px) {
 .waterfall-container {
 flex-direction: column;
 }

 .waterfall-item.horizontal {
 height: 180px;
 }

 .waterfall-item.vertical {
 height: 400px;
 }
}

/* 更小屏幕的优化 */
@media (max-width: 480px) {
 .waterfall-item.horizontal {
 height: 160px;
 }

 .waterfall-item.vertical {
 height: 360px;
 }
}

/* 灯箱样式 */
.lightbox {
 display: none;
 position: fixed;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 background: rgba(0, 0, 0, 0.9);
 z-index: 9999;
 justify-content: center;
 align-items: center;
 cursor: pointer;
 animation: fadeIn 0.3s ease;
}

.lightbox.active {
 display: flex;
}

.lightbox img {
 max-width: 90%;
 max-height: 90%;
 object-fit: contain;
 border-radius: 4px;
 animation: zoomIn 0.3s ease;
}

.lightbox-close {
 position: absolute;
 top: 20px;
 right: 30px;
 color: white;
 font-size: 40px;
 font-weight: bold;
 cursor: pointer;
 transition: opacity 0.3s;
 line-height: 1;
 user-select: none;
}

.lightbox-close:hover {
 opacity: 0.7;
}

.lightbox-nav {
 position: absolute;
 top: 50%;
 transform: translateY(-50%);
 color: white;
 font-size: 40px;
 font-weight: bold;
 cursor: pointer;
 transition: opacity 0.3s;
 user-select: none;
 padding: 20px;
}

.lightbox-nav:hover {
 opacity: 0.7;
}

.lightbox-prev {
 left: 20px;
}

.lightbox-next {
 right: 20px;
}

@keyframes fadeIn {
 from { opacity: 0; }
 to { opacity: 1; }
}

@keyframes zoomIn {
 from {
 transform: scale(0.8);
 opacity: 0;
 }
 to {
 transform: scale(1);
 opacity: 1;
 }
}

/* 加载动画 */
.waterfall-item.loading::before {
 content: '';
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
 background-size: 200% 100%;
 animation: loading 1.5s infinite;
}

.dark .waterfall-item.loading::before {
 background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%);
 background-size: 200% 100%;
}

@keyframes loading {
 0% { background-position: 200% 0; }
 100% { background-position: -200% 0; }
}
&lt;/style&gt;
&lt;div class="waterfall-container" id="gallery"&gt;
 &lt;!-- JavaScript 会自动创建3列并按模式分配图片 --&gt;
&lt;/div&gt;
&lt;!-- 灯箱 --&gt;
&lt;div class="lightbox" id="lightbox"&gt;
 &lt;span class="lightbox-close" id="lightbox-close"&gt;&amp;times;&lt;/span&gt;
 &lt;span class="lightbox-nav lightbox-prev" id="lightbox-prev"&gt;&amp;#8249;&lt;/span&gt;
 &lt;span class="lightbox-nav lightbox-next" id="lightbox-next"&gt;&amp;#8250;&lt;/span&gt;
 &lt;img id="lightbox-img" src="" alt=""&gt;
&lt;/div&gt;
&lt;script&gt;
(function() {
 // Set page title
 document.title = 'Moments - Koala';

 const gallery = document.getElementById('gallery');
 const lightbox = document.getElementById('lightbox');
 const lightboxImg = document.getElementById('lightbox-img');
 const lightboxClose = document.getElementById('lightbox-close');
 const lightboxPrev = document.getElementById('lightbox-prev');
 const lightboxNext = document.getElementById('lightbox-next');

 let currentImageIndex = 0;
 let images = [];
 let imageData = [];

 // 从 JSON 文件加载图片数据
 async function loadImageData() {
 try {
 const response = await fetch('/images/moments/moments.json');
 if (!response.ok) {
 throw new Error('Failed to load moments data');
 }
 imageData = await response.json();
 // 自动生成 src 和 alt
 imageData = imageData.map((item) =&gt; ({
 ...item,
 src: `/images/moments/p${item.id}.jpg`,
 alt: `${item.location || '未知地点'} - ${item.id}`
 }));
 initGallery();
 } catch (error) {
 console.error('Error loading moments data:', error);
 // 如果加载失败,显示错误信息
 gallery.innerHTML = '&lt;p style="text-align: center; color: #999;"&gt;加载图片数据失败，请检查 /static/images/moments/moments.json 文件&lt;/p&gt;</description></item><item><title>2025 年的璀璨冒险人</title><link>https://blog.therainisme.com/zh/posts/the-brilliant-adventurers-of-2025/</link><pubDate>Wed, 01 Jan 2025 00:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/posts/the-brilliant-adventurers-of-2025/</guid><description>&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Wu9COUDK1h0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;这个世界 是什么模样&lt;br&gt;
走过山水一程 梦一场&lt;br&gt;
那迎风呐喊的海浪&lt;br&gt;
要多久 多久才会遇上&lt;br&gt;
冒险的人 是否会感伤&lt;br&gt;
终其璀璨一生 谁收藏&lt;br&gt;
那深埋心底的愿望&lt;br&gt;
要多久 多久才会登场&lt;/p&gt;
&lt;p&gt;沿途一身泥泞 跌撞的是我&lt;br&gt;
忍痛缓行不回头的 也是我&lt;br&gt;
就让一路曲折 每一道伤口&lt;/p&gt;
&lt;p&gt;洗净我的脆弱&lt;br&gt;
原来 咽回去的泪&lt;br&gt;
才能 淹没了脆弱&lt;br&gt;
你 发誓更勇敢 一生与梦相拥&lt;br&gt;
还 想要继续吗&lt;br&gt;
要 逆风不退啊&lt;br&gt;
让璀璨住进 你眼眸&lt;br&gt;
别怕 未来的模样&lt;br&gt;
辜负 曾经的凝望&lt;br&gt;
有 多少理想 就有 多少次传唱&lt;br&gt;
那 沿途的风浪&lt;br&gt;
也 不过就这样&lt;br&gt;
这一路光景 有你在身旁&lt;/p&gt;</description></item><item><title>About</title><link>https://blog.therainisme.com/zh/about/</link><pubDate>Wed, 01 Jan 2025 00:00:00 +0800</pubDate><guid>https://blog.therainisme.com/zh/about/</guid><description>&lt;link rel="stylesheet" href="https://blog.therainisme.com/about/about.css"&gt;
&lt;p&gt;⚡ I do my best to seek happiness in life and spread it around. If you often find me by your side, it&amp;rsquo;s simply because being with you brings me joy.&lt;/p&gt;
&lt;div style="width: 100%; overflow-x: auto;"&gt;
 &lt;img src="https://ghchart.rshah.org/therainisme" alt="GitHub Contributions" style="width: 100%; max-width: 800px; margin-top: 0; margin-bottom: 0" /&gt;
&lt;/div&gt;
&lt;h2 id="albums"&gt;Albums&lt;/h2&gt;
&lt;p&gt;This is my favorite album at the moment - &lt;em&gt;SHENSELF&lt;/em&gt; by Zhou Shen.&lt;/p&gt;
&lt;div style="display: flex; gap: 20px; flex-wrap: wrap; margin: 20px 0; align-items: flex-start; overflow: visible;"&gt;
 &lt;a href="https://shenself.net" target="_blank" rel="noopener noreferrer" style="text-decoration: none;"&gt;
 &lt;div style="width: 200px; height: 200px; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: transform 0.3s ease, box-shadow 0.3s ease; cursor: pointer; background: #f5f5f5;" onmouseover="this.style.transform='scale(1.05)'; this.style.boxShadow='0 8px 20px rgba(0,0,0,0.2)';" onmouseout="this.style.transform='scale(1)'; this.style.boxShadow='0 4px 12px rgba(0,0,0,0.15)';"&gt;
 &lt;img src="https://blog.therainisme.com/about/shenself.png" alt="周深 - 反深代词" style="margin: 0; width: 100%; height: 100%; object-fit: contain;" /&gt;
 &lt;/div&gt;
 &lt;/a&gt;
 &lt;div id="music-player" style="width: 200px; height: 200px; padding: 16px; border-radius: 12px; display: flex; flex-direction: column; position: relative;"&gt;
 &lt;button id="play-button" style="position: absolute; top: 12px; right: 12px; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0;"&gt;
 &lt;svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"&gt;
 &lt;path d="M8 5v14l11-7z"/&gt;
 &lt;/svg&gt;
 &lt;/button&gt;
 &lt;div style="flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 6px;"&gt;
 &lt;div style="font-weight: 500; font-size: 0.95em;"&gt;重启&lt;/div&gt;
 &lt;div style="font-size: 0.8em; opacity: 0.8;"&gt;周深&lt;/div&gt;
 &lt;div id="lyric-display" style="font-size: 0.75em; opacity: 0.9; line-height: 1.3; margin-top: 4px;"&gt;200年后的世界，每个人都拥有“重启”的机会……&lt;/div&gt;
 &lt;div id="progress-container" style="height: 4px; background: rgba(0, 0, 0, 0.1); border-radius: 2px; position: relative; margin-top: auto;"&gt;
 &lt;div id="progress-bar" style="height: 100%; background: rgba(76, 175, 80, 0.8); border-radius: 2px; width: 0%; pointer-events: none;"&gt;&lt;/div&gt;
 &lt;/div&gt;
 &lt;div id="time-display" style="font-size: 0.7em; opacity: 0.6; text-align: right; margin-top: 2px;"&gt;0:00 / 0:00&lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;audio id="audio-player" src="https://blog.therainisme.com/about/reset.mp3" preload="metadata"&gt;&lt;/audio&gt;&lt;/p&gt;</description></item><item><title>Friends</title><link>https://blog.therainisme.com/zh/friends/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://blog.therainisme.com/zh/friends/</guid><description>&lt;style&gt;
.friends-container {
 margin-top: 50px;
 margin-bottom: 50px;
 min-height: 500px;
}

.friend-columns {
 width: 100%;
 display: flex;
 justify-content: space-evenly;
 gap: 20px;
}

.friend-card-outer {
 position: relative;
 width: 350px;
 min-height: 450px;
}

.friend-card {
 width: 350px;
 animation: friend-card-fadein 0.3s;
}

.friend-card.fadeout {
 position: absolute;
 top: 0;
 left: 0;
 animation: friend-card-fadeout 0.3s;
 opacity: 0;
 pointer-events: none;
}

@keyframes friend-card-fadein {
 from {
 opacity: 0;
 transform: translateX(20px);
 }
 to {
 opacity: 1;
 transform: translateX(0);
 }
}

@keyframes friend-card-fadeout {
 from {
 opacity: 1;
 transform: translateX(0);
 }
 to {
 opacity: 0;
 transform: translateX(-20px);
 }
}

.friend-card .card {
 width: 350px;
 background: var(--bg, #faf8f1);
 border: 1px solid rgba(0, 0, 0, 0.1);
 border-radius: 12px;
 overflow: hidden;
 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}

.dark .friend-card .card {
 background: rgba(255, 255, 255, 0.05);
 border-color: rgba(255, 255, 255, 0.1);
}

.friend-card .card__image {
 width: 100%;
 padding-top: 100%;
 position: relative;
 overflow: hidden;
 margin-top: 0;
}

.friend-card .card__image img {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 object-fit: cover;
 margin-top: 0;
}

.friend-card .card__body {
 padding: 16px;
}

.friend-card .card__body h2 {
 margin: 0 0 8px 0;
}

.friend-card .card__body p {
 margin: 8px 0;
 line-height: 1.5;
 opacity: 0.8;
}

.friend-card .card__body p.bio {
 opacity: 0.6;
}

.friend-card .card__footer {
 padding: 0 16px 16px;
}

.friend-card .card__footer a {
 display: block;
 text-align: center;
 padding: 10px 20px;
 background: #333;
 color: #fff;
 text-decoration: none;
 border-radius: 8px;
 transition: background 0.2s;
}

.friend-card .card__footer a:hover {
 background: #555;
}

.dark .friend-card .card__footer a {
 background: rgba(255, 255, 255, 0.15);
}

.dark .friend-card .card__footer a:hover {
 background: rgba(255, 255, 255, 0.25);
}

.friend-list {
 display: flex;
 flex-direction: column;
 justify-content: flex-start;
 flex-wrap: wrap;
 height: 400px;
 gap: 6px;
}

.friend-item {
 user-select: none;
 cursor: pointer;
 transition: all 0.3s;
 filter: brightness(0.7);
 height: 50px;
 width: 50px;
 margin: 3px 5px;
 position: relative;
}

.friend-item:hover {
 filter: brightness(0.9);
}

.friend-item.current {
 filter: brightness(1);
}

.friend-item img {
 height: 50px;
 width: 50px;
 border-radius: 20%;
 margin: 0;
 display: block;
 pointer-events: none;
}


@media screen and (max-width: 600px) {
 .friends-container {
 margin-top: 30px;
 margin-bottom: 30px;
 }
 .friend-card-outer {
 width: 100%;
 max-width: 350px;
 margin: 0 0 20px 0;
 min-height: auto;
 }
 .friend-card,
 .friend-card .card {
 width: 100%;
 max-width: 350px;
 }
 .friend-list {
 flex-direction: row;
 height: auto;
 justify-content: center;
 }
 .friend-columns {
 flex-direction: column;
 align-items: center;
 }
}
&lt;/style&gt;
&lt;div id="friends-app" class="friends-container"&gt;&lt;/div&gt;
&lt;script&gt;
(function() {
 // Set page title
 document.title = 'Friends - Koala';
 
 const friendsData = [
 {
 github: "visualDust",
 name: "VisualDust",
 intro: "统帅全能王，地位无可动摇，现在在啊美丽肯读博士。",
 url: "https://blog.gong.host/"
 },
 {
 github: "PommesPeter",
 name: "PommesPeter",
 intro: "语言全能王，准备和统帅去读博士，现在在成电。",
 url: "https://memo.sylin.host/"
 },
 {
 github: "lideming",
 name: "Lideming",
 intro: "（编程）语言全能王，大厂自由卡，现在在腾讯。",
 url: "https://yuuza.net"
 },
 {
 github: "AndPuQing",
 name: "PuQing",
 intro: "骚话全能王，编程届爱迪生，现在在西电降维打击。",
 url: "https://puqing.work/"
 },
 {
 github: "papercube",
 name: "PaperCube",
 intro: "日常全能王，统帅的心腹，隐匿的强者，现在在字节。",
 url: "https://github.com/papercube"
 },
 {
 github: "AndSonder",
 name: "AndSonder",
 intro: "无敌自卷人，无限进步者，究极 J 人，现在在成电。",
 url: "http://space.keter.top"
 }
 ];

 // Shuffle array
 function shuffle(array) {
 const arr = [...array];
 for (let i = arr.length - 1; i &gt; 0; i--) {
 const j = Math.floor(Math.random() * (i + 1));
 [arr[i], arr[j]] = [arr[j], arr[i]];
 }
 return arr;
 }

 // Fetch GitHub bio for a user
 async function fetchGitHubBio(username) {
 try {
 const response = await fetch(`https://api.github.com/users/${username}`);
 if (response.ok) {
 const data = await response.json();
 return data.bio || '';
 }
 } catch (e) {
 console.error(`Failed to fetch bio for ${username}:`, e);
 }
 return '';
 }

 // Initialize friends with pic URL
 const friends = shuffle(friendsData).map(f =&gt; ({
 ...f,
 pic: `https://github.com/${f.github}.png`,
 bio: '',
 bioLoaded: false,
 bioLoading: false
 }));
 
 let current = 0;
 let previous = 0;

 function createCard(data, fadeout = false) {
 const bioHtml = data.bio ? `&lt;p class="bio" style="font-size: var(--text-base);"&gt;${data.bio}&lt;/p&gt;</description></item><item><title>Tools</title><link>https://blog.therainisme.com/zh/tools/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://blog.therainisme.com/zh/tools/</guid><description>&lt;style&gt;
#sha256-output-container {
 position: relative;
}
#sha256-copy {
 opacity: 0;
 transition: opacity 0.2s;
 pointer-events: none;
}
#sha256-output-container:hover #sha256-copy {
 opacity: 1;
 pointer-events: auto;
}
#sha256-input,
#sha256-output {
 border-color: #ccc;
 background-color: #fff;
}
#sha256-output {
 background-color: #f5f5f5;
}
#sha256-copy {
 border-color: #ccc;
 background-color: #fff;
 color: #000;
}
.dark #sha256-input,
.dark #sha256-output {
 border-color: #444;
 background-color: #1a1a1a;
 color: #fff;
}
.dark #sha256-output {
 background-color: #2a2a2a;
}
.dark #sha256-copy {
 border-color: #444;
 background-color: #2a2a2a;
 color: #fff;
}
&lt;/style&gt;
&lt;div id="sha256-tool"&gt;
 &lt;h2 id="sha256-calculator"&gt;SHA256 Calculator&lt;/h2&gt;
 &lt;div style="margin-bottom: 1rem;"&gt;
 &lt;label for="sha256-input" style="display: block; margin-bottom: 0.5rem; font-weight: 500;"&gt;Input:&lt;/label&gt;
 &lt;input type="text" id="sha256-input" style="width: 100%; padding: 0.5rem; border: 1px solid; border-radius: 4px; font-family: monospace; font-size: var(--text-base);" placeholder="Enter text to calculate SHA256..."&gt;
 &lt;/div&gt;
 &lt;div style="margin-bottom: 1rem;"&gt;
 &lt;label for="sha256-output" style="display: block; margin-bottom: 0.5rem; font-weight: 500;"&gt;Output:&lt;/label&gt;
 &lt;div id="sha256-output-container"&gt;
 &lt;input type="text" id="sha256-output" readonly style="width: 100%; padding: 0.5rem; border: 1px solid; border-radius: 4px; font-family: monospace; pointer-events: none; font-size: var(--text-base);" placeholder="SHA256 hash will appear here..."&gt;
 &lt;button id="sha256-copy" style="position: absolute; right: 0.5rem; top: 50%; transform: translateY(-50%); padding: 0.25rem 0.75rem; border: 1px solid; border-radius: 4px; cursor: pointer; font-size: 0.875rem;"&gt;Copy&lt;/button&gt;
 &lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;
&lt;div id="ip-info"&gt;&lt;/div&gt;
&lt;script&gt;
(function() {
 document.title = 'Tools - Koala';

 // SHA256 functionality
 async function sha256(message) {
 const msgBuffer = new TextEncoder().encode(message);
 const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
 const hashArray = Array.from(new Uint8Array(hashBuffer));
 const hashHex = hashArray.map(b =&gt; b.toString(16).padStart(2, '0')).join('');
 return hashHex;
 }

 const sha256Input = document.getElementById('sha256-input');
 const sha256Output = document.getElementById('sha256-output');
 const sha256Copy = document.getElementById('sha256-copy');

 if (sha256Input &amp;&amp; sha256Output) {
 sha256Input.addEventListener('input', async function() {
 const text = this.value;
 if (text) {
 const hash = await sha256(text);
 sha256Output.value = hash;
 } else {
 sha256Output.value = '';
 }
 // Reset copy button when input changes
 if (sha256Copy) {
 sha256Copy.textContent = 'Copy';
 }
 });
 }

 if (sha256Copy &amp;&amp; sha256Output) {
 sha256Copy.addEventListener('click', function() {
 const textToCopy = sha256Output.value || '';
 navigator.clipboard.writeText(textToCopy).then(function() {
 sha256Copy.textContent = '✓';
 }).catch(function(err) {
 console.error('Copy failed:', err);
 sha256Copy.textContent = '✓';
 });
 });
 }

 async function fetchIP() {
 try {
 const response = await fetch('https://api.ip.sb/geoip');
 const data = await response.json();
 return `${data.ip} ${data.country || ''} ${data.city || ''}`.replace(/\s+/g, ' ').trim();
 } catch (error) {
 return null;
 }
 }

 async function loadIP() {
 const ip = await fetchIP();
 
 if (ip) {
 document.getElementById('ip-info').innerHTML = `
 &lt;h2 id="ip-information"&gt;IP Information&lt;/h2&gt;
 &lt;p style="margin-top: 0; margin-bottom: 0;"&gt;${ip} &amp;nbsp;|&amp;nbsp; &lt;span style="opacity: 0.6; font-size: 0.85em;"&gt;Powered by &lt;a href="https://ip.sb" target="_blank"&gt;IP.SB&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description></item></channel></rss>