2026-02-02 00:00:00

在 WireGuard 虚拟网络环境下,我尝试 SSH 连接远程主机时,TCP 连接可以建立,但会话随即卡死。
使用 ssh -v 开启详细调试模式,发现日志停滞在密钥交换(Key Exchange)的最后一步:
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
...
debug1: kex: algorithm: curve25519-sha256
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY <-- 卡死在此处,随后超时
MTU 黑洞 (MTU Black Hole)。
SSH 在密钥交换(KEX)阶段会发送较大的数据包。WireGuard 协议本身会增加头部开销(Overhead)。当 [SSH Payload + TCP/IP Headers + WireGuard Overhead] 的总长度超过物理链路路径上的最小 MTU(通常受限于 PPPoE 拨号或中间路由策略)时,数据包会被静默丢弃。
由于中间链路可能禁用了 ICMP,导致发送端无法收到 “Fragmentation Needed” 通知,从而造成连接“假死”。
WireGuard 官方推荐将 MTU 设置为保守值 1280 以适配绝大多数网络环境。
2026-02-01 00:00:00

最近在macOS上安装应用程序时,是否遇到过“已损坏,无法打开”的提示。经过一番搜索,这是macOS的安全机制在作祟。
macOS包含一套名为“Gatekeeper”的安全系统,它会检查应用程序是否来自已知开发者,并确保软件未被篡改。当您从非App Store来源下载应用时,系统可能会阻止其运行,并显示“已损坏”的警告。
打开“终端”应用程序(可以在“应用程序”>“实用工具”中找到),输入以下命令并按回车:
sudo spctl --master-disable
根据提示输入您的管理员密码。然后打开“系统偏好设置”>“安全性与隐私”>“通用”,您会看到“允许从以下位置下载的应用程序”选项,现在可以选择“任何来源”了:

再次打开“终端”,输入以下命令(注意命令末尾有一个空格):
sudo xattr -dr com.apple.quarantine
不按回车,而是打开“访达”,找到无法打开的应用程序,将其拖拽到终端窗口中。此时终端会自动填充应用程序的完整路径,然后按回车执行命令并输入管理员密码。大概是这个样子
;
sudo xattr -dr com.apple.quarantine /Applications/MyApp.app
完成前两个步骤后,您现在可以正常双击打开应用程序了。
sudo spctl --master-enable
macOS使用com.apple.quarantine属性标记从互联网下载的文件,这个属性会告诉Gatekeeper在首次打开时检查应用程序。移除这个属性或完全禁用Gatekeeper可以绕过这些检查。
这个3步方案适用于大多数从非官方渠道下载的macOS应用程序。
2026-01-20 00:00:00

最近opencode很火,我也在macOS上装了。这篇文章记录一下安装和使用过程。
OpenCode is an open source agent that helps you write code in your terminal, IDE, or desktop.
最简单的方式是使用Homebrew:
brew install anomalyco/tap/opencode

启动OpenCode并配置模型:
cd ~/Workspace # 进入你的工作目录
opencode # 启动OpenCode

在TUI界面中,输入 /connect 配置模型提供商:
首次使用,运行 /init 让OpenCode分析你的项目结构。

掌握这几个核心命令就能开始使用:
/help # 查看所有命令
@文件名 # 引用文件内容
!命令 # 执行shell命令
/undo # 撤销操作
/redo # 重做操作
Tab键 # 切换Plan/Build模式
Esc # 退出、打断当前操作
先拿我的blog项目进行练手:
请分析我这个blog项目的结构和功能,然后告诉我可以用OpenCode做什么改进?






其他类似的上手问答工作都可以试试:
1. 请问你会做些什么
2. 检查macOS版本
3. 显示内存使用情况
4. 列出Homebrew安装的包
5. 检查磁盘空间



OpenCode 云端模型:
在输入框输入 /models 即可切换models


OpenCode 原生支持连接本地 Ollama 实例,无需 API Key。
# 启动服务(后台运行)
ollama serve
# 拉取代码专用模型
ollama pull qwen3-coder-next:q4_K_M
⚠️ 重要提示:截至 2026.2.4 macOS 正式版 Ollama不支持
qwen3-coder-next等最新模型。 请从 GitHub 下载最新的 RC 版本:
- 访问 https://github.com/ollama/ollama/releases
- 下载 macOS RC 版本的 dmg 文件(不是 tar.gz!)
- 打开 dmg 文件,将 Ollama 拖到应用程序文件夹
- 启动 Ollama(Launchbar 或 Spotlight 搜索 “Ollama”)
vi ~/.config/opencode/opencode.json
{
"provider": {
"ollama": {
"npm": "@ai-sdk/openai-compatible",
"name": "Ollama(local)",
"options": {
"baseURL": "http://localhost:11434/v1"
},
"models": {
"qwen3-coder-next:q4_K_M": {
"name": "qwen3-coder-next:q4_K_M"
}
}
}
}
}
vi ~/.config/opencode/oh-my-opencode.json
{
"agents": {
"hephaestus": {
"model": "ollama/qwen3-coder-next:q4_K_M"
},
"oracle": {
"model": "ollama/qwen3-coder-next:q4_K_M"
},
"prometheus": {
"model": "ollama/qwen3-coder-next:q4_K_M"
},
"sisyphus": {
"model": "ollama/qwen3-coder-next:q4_K_M"
}
},
"categories": {
"visual-engineering": {
"model": "opencode/minimax-m2.1-free"
},
"ultrabrain": {
"model": "opencode/minimax-m2.1-free"
},
"quick": {
"model": "opencode/minimax-m2.1-free"
}
}
}
2026-01-19 00:00:00

最近重置了 Mac 系统,需要重新配置 Ruby 和 Jekyll 环境来运行我的静态博客。相比于直接使用系统 Ruby,这次选择了更优雅的 rbenv 进行版本管理。整个过程遇到不少“坑”,特此记录以备忘,希望这篇记录也能帮你绕开这些坑。
rbenv 将 Ruby 环境和所有 Gem 安装在你的用户目录下,实现完全的隔离管理,是当前 Ruby 社区推荐的最佳实践。
# 1. 通过 Homebrew 安装 rbenv 和 ruby-build(用来编译安装 Ruby)
brew install rbenv ruby-build
# 2. 初始化 rbenv,并按照提示把下面这行加到 ~/.zshrc 里
rbenv init
echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc
source ~/.zshrc # 让配置生效
# 3. 安装一个稳定的 Ruby 版本(这里选了 3.4.4,因为我系统显示最新的就是3.4.4)
rbenv install --list-all
rbenv install 3.4.4
rbenv global 3.4.4 # 设为默认版本
rbenv rehash # 重要:重建命令链接
# 4. 验证一下,确保 ruby 和 gem 命令指向的是 rbenv 安装的版本
which ruby # 应该显示 ~/.rbenv/shims/ruby
which gem # 应该显示 ~/.rbenv/shims/gem
ruby -v # 应该显示 3.4.4
# 5. 现在可以安全安装 Jekyll 了
gem install jekyll
本以为万事大吉,在博客目录下输入 jekyll s,结果错误连环出现。
直接报错,说项目 Gemfile 里锁定的 Jekyll 版本(4.3.2)和我刚全局安装的版本(4.4.1)对不上。

解决:在项目目录下,让 Bundler 根据 Gemfile 重新安装所有依赖。
bundle install

运行 bundle install 时,在编译 nokogiri 时卡住了,报错提示在找 x86_64 的库,但我的是 ARM 芯片(Apple Silicon)。
ld: warning: ignoring file '/opt/homebrew/Cellar/xz/5.8.1/lib/liblzma.dylib': found architecture 'arm64', required architecture 'x86_64'
原因:我的机器上好像混用了两个 Homebrew(Intel 和 ARM 版)。默认的 brew 命令指向了 Intel 版本。
解决:
brew 路径:which brew。我的是 /usr/local/bin/brew(Intel版)。eval $(/opt/homebrew/bin/brew shellenv)
bundle install,编译就通过了。public_suffix 版本激活冲突再次尝试 jekyll s,又出现新错误:全局的 public_suffix (7.0.2) 和项目需要的 (5.0.4) 冲突。
解决:改用 bundle exec 命令,严格使用项目 Gemfile.lock 里锁定的版本。
bundle exec jekyll s
以为终于行了,结果连续报错:
cannot load such file -- csvcannot load such file -- base64cannot load such file -- bigdecimal原因:Ruby 3.4 开始,像 csv、base64、bigdecimal、zlib、openssl 这些以前默认就有的标准库,现在需要单独作为 gem 安装了。而我的博客用的 Jekyll 版本比较旧,还依赖它们。
解决:在项目目录下,把这些缺失的库一次性补上。
bundle add csv base64 webrick bigdecimal zlib openssl
完成以上所有步骤后,再次运行:
bundle exec jekyll s
熟悉的启动信息终于出现了,浏览器打开 http://localhost:4000,博客本地预览恢复正常。
bundle exec 是护身符:在项目目录下运行任何 gem 相关的命令,都习惯性加上它,能避免很多奇怪的版本冲突。brew,能省去很多编译麻烦。bundle add 装啥,csv、base64、bigdecimal、zlib、openssl 这几个是常客。2026-01-17 00:00:00

最近在使用 Jekyll 开发博客时,遇到了一个典型问题:Gemfile 和 Gemfile.lock 文件 在本地和生产环境需要不同的配置。
git update-index --assume-unchanged
Git 提供了一个优雅的解决方案:assume-unchanged 标志。
assume-unchanged 是 Git 的一个内部标志,它告诉 Git:
“假设这个文件没有变化,不要检查它的修改状态,也不要让我提交它。”
# 从暂存区移除Gemfile文件
git restore --staged ../Gemfile ../Gemfile.lock
# 告诉 Git 忽略特定文件的本地修改
git update-index --assume-unchanged Gemfile
git update-index --assume-unchanged Gemfile.lock
# 查看哪些文件被标记为 assume-unchanged
git ls-files -v | grep '^h'
# 提交其他文件
git commit -m "更新博客文章,忽略Gemfile本地修改"
# 查看状态确认
git status
# 需要更新配置文件时
# 先恢复跟踪(撤销忽略)
git update-index --no-assume-unchanged Gemfile
git update-index --no-assume-unchanged Gemfile.lock
# 修改并提交
git add Gemfile
git commit -m "更新Gemfile依赖"
# 重新标记为忽略
git update-index --assume-unchanged Gemfile
git update-index --assume-unchanged Gemfile.lock
很多人会混淆 assume-unchanged 和 .gitignore,它们有本质区别:
| 特性 | assume-unchanged |
.gitignore |
|---|---|---|
| 用途 | 忽略已跟踪文件的修改 | 忽略未跟踪的文件 |
| 效果 | 文件仍在版本控制中,只是不检查修改 | 文件完全不被版本控制 |
| 适用场景 | 本地配置文件、环境变量文件 | 构建产物、日志文件、IDE配置 |
| 共享性 | 本地设置,不共享给他人 | 提交到仓库,团队成员共享 |
skip-worktree 的区别Git 还有一个类似的标志:skip-worktree。
# skip-worktree 的用法
git update-index --skip-worktree Gemfile
# 查看区别
git ls-files -v | grep '^S' # skip-worktree 文件
git ls-files -v | grep '^h' # assume-unchanged 文件
主要区别:
assume-unchanged:性能优化,告诉Git文件不太可能改变skip-worktree:功能标志,明确表示”不要更新我的工作树”对于配置文件管理,skip-worktree 是更安全的选择,因为它能防止 Git 的各种操作覆盖你的本地文件。
分支切换问题:
当你在标记了 assume-unchanged 的分支之间切换时,如果文件有冲突,Git 可能会报错。
# 假设团队成员更新了仓库中的 Gemfile
git pull
# 由于本地文件被标记,更新可能不会应用到你的工作副本
# 创建一个脚本帮助管理
cat > git-ignored-files.sh << 'EOF'
#!/bin/bash
echo "当前被忽略的文件:"
git ls-files -v | grep -E "^[hs]"
EOF
chmod +x git-ignored-files.sh
对于配置文件管理,还有更健壮的方案:
# 仓库中保存模板
_config.example.yml
Gemfile.example
# 本地复制并重命名
cp _config.example.yml _config.yml
cp Gemfile.example Gemfile
# _config.yml
url: <%= ENV['JEKYLL_SITE_URL'] || 'http://localhost:4000' %>
# 使用不同的配置文件
jekyll build --config _config.yml,_config.local.yml
git update-index --assume-unchanged 是一个强大的工具,特别适合管理那些需要在版本控制中保留,但又不想提交本地修改的文件。
最后,如果你经常忘记哪些文件被标记了,可以在 .gitconfig 中添加别名:
[alias]
ignored = !git ls-files -v | grep \"^[hs]\"
hide = update-index --assume-unchanged
unhide = update-index --no-assume-unchanged
然后使用更简洁的命令:
git ignored # 查看被忽略的文件
git hide Gemfile # 隐藏文件
git unhide Gemfile # 取消隐藏
2025-06-26 00:00:00

这篇文章简单记录自建DNS over HTTPS(DoH)服务的步骤。
100.100.100.100:53
satishweb/doh-server Docker容器(监听100.100.100.100:1053)# 停止并删除旧容器
docker stop doh && docker rm doh
# 启动DoH容器
docker run -d --restart unless-stopped \
--network host \
--name doh \
-e UPSTREAM_DNS_SERVER="udp:100.100.100.100:53" \
-e DOH_HTTP_PREFIX="/dns-query" \
-e DOH_SERVER_LISTEN="100.100.100.100:1053" \
-e DOH_SERVER_TIMEOUT="10" \
-e DOH_SERVER_TRIES="3" \
-e DOH_SERVER_VERBOSE="true" \
satishweb/doh-server
关键参数说明:
UPSTREAM_DNS_SERVER:上游DNS地址(需可被容器访问)DOH_SERVER_LISTEN:容器监听的本地地址和端口DOH_HTTP_PREFIX:DoH请求路径(必须与Nginx配置一致)在Nginx站点配置中(如/etc/nginx/sites-available/default)添加:
server {
listen 443 ssl;
server_name blog.kelu.org; # 替换为您的域名
# SSL证书配置(必需)
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
# DoH代理配置
location = /dns-query {
allow 100.100.100.5; # 允许访问的客户端IP
deny all; # 禁止其他IP
proxy_pass http://100.100.100.100:1053; # 指向DoH容器
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 其他配置...
}
操作命令:
sudo nginx -t && sudo systemctl reload nginx # 测试并重载配置
直接访问地址,验证是否成功:
https://aa.bb.com/dns-query?name=baidu.com&type=A

手工生成配置文件 doh.mobileconfig:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>DNSSettings</key>
<dict>
<key>DNSProtocol</key>
<string>HTTPS</string>
<key>ServerURL</key>
<string>https://blog.kelu.org/dns-query</string> <!-- 改为您的域名 -->
</dict>
<key>PayloadDescription</key>
<string>Configures DNS over HTTPS</string>
<key>PayloadDisplayName</key>
<string>DoH DNS Server</string>
<key>PayloadIdentifier</key>
<string>com.yourdomain.dns</string>
<key>PayloadType</key>
<string>com.apple.dnsSettings.managed</string>
<key>PayloadUUID</key>
<string>065AB183-5E34-4794-9BEB-B5327CF61F27</string> <!-- 用uuidgen生成 -->
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>Install to enable DNS over HTTPS</string>
<key>PayloadDisplayName</key>
<string>Custom DoH Configuration</string>
<key>PayloadIdentifier</key>
<string>com.yourdomain.dohprofile</string>
<key>PayloadUUID</key>
<string>030E6D6F-69A2-4515-9D77-99342CB9AE76</string> <!-- 用uuidgen生成 -->
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
ServerURL 为您的HTTPS地址
- 使用 uuidgen 命令生成新的 PayloadUUID
安装配置:
双击 .mobileconfig 文件导入macOS

提醒打开系统设定,在设备管理里可以看到:

双击后安装即可。
测试DoH服务:
curl -H 'content-type: application/dns-message' \
"https://blog.kelu.org/dns-query?dns=q80BAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB"
正常应返回加密的DNS响应。
客户端检查:
在macOS终端执行 scutil --dns | grep 'nameserver\[0\]'
前后对比,可以看到多了一个地址为 127.0.0.1 的 dns解析:

查看日志:docker logs doh
日志大概长这个样:
