<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[Drofyn]]></title> 
<atom:link href="https://writecho.com/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[]]></description>
<link>https://writecho.com/</link>
<language>zh-cn</language>

<item>
    <title>把自己的工作流放进菜单栏：OmniPilot 项目介绍</title>
    <link>https://writecho.com/软件开发/6.html</link>
    <description><![CDATA[<p>我一直觉得，菜单栏应用最适合做两类事情：把经常用的小功能放在手边，以及替自己照看那些需要长期运行的任务。于是我做了 OmniPilot——一个常驻 macOS 菜单栏的个人助手。</p>
<p>它不是想取代某个成熟工具，也不打算做成什么都管的“大杂烩”。OmniPilot 只收纳那些确实贴合我自己日常习惯的功能。目前，它主要做两件事：管理 SSH 隧道，以及保存文本剪贴板历史。</p>
<h2>从 SSH 隧道开始</h2>
<p>我在 <code>~/.ssh/config</code> 里已经配置好了服务器和端口转发，但每次开机、待机或网络切换之后，都需要手动启动隧道，还得留意连接有没有断。</p>
<p>OmniPilot 直接读取 SSH 配置，找出声明了 <code>LocalForward</code> 或 <code>RemoteForward</code> 或 <code>DynamicForward</code> 的 Host，并为每个 Host 启动一个 <code>ssh -N &lt;alias&gt;</code> 进程。隧道意外退出时，应用会按 <code>1、2、5、10、30</code> 秒的间隔自动重连；连续失败则通过系统通知提醒我。整个过程不修改 SSH 配置，也不接管密钥或认证，仍然使用系统已有的 <code>ssh-agent</code>。</p>
<p>这种设计的好处是，连接信息仍由 OpenSSH 管理，OmniPilot 只负责“启动、监督和展示状态”。菜单栏里可以查看每条隧道及其转发信息，也可以单独或批量启停。</p>
<h2>再加一个常见的小功能</h2>
<p>剪贴板历史是我后来加入的第二个功能。它每 0.8 秒检查一次系统剪贴板的变化，只有内容变化时才读取，并且当前只保存文本。</p>
<p>菜单栏面板里可以查看历史、删除单条记录，或清空全部内容。历史条数可以选择 10、20、30、50 或 100 条，默认 30 条。点击一条历史记录会把文本写回系统剪贴板，之后我可以在目标应用里按 <code>⌘V</code> 粘贴；它不会替我模拟键盘输入。</p>
<p>剪贴板内容保存在 <code>~/Library/Application Support/OmniPilot/clipboard-history.json</code>，条数设置则通过 <code>UserDefaults</code> 保存。历史文件是普通 JSON，没有加密，所以它可能包含敏感文本；应用会过滤常见的隐藏、临时和密码管理器标记，并提供单条删除和清空操作，但这不等于安全存储。</p>
<h2>菜单栏之外的代码结构</h2>
<p>这个项目是我学习 Xcode 和 Swift 开发时逐步搭起来的。代码按“应用外壳”和“可测试的核心逻辑”分成两层：</p>
<ul>
<li><code>Sources/OmniPilot</code> 是 macOS App 本身，放菜单栏界面、剪贴板监控、通知和登录项等系统集成。</li>
<li><code>Packages/OmniPilotKit</code> 是本地 Swift Package，放 SSH 配置解析、隧道进程管理，以及剪贴板历史模型和持久化逻辑。</li>
<li><code>project.yml</code> 描述 Xcode 工程，由 XcodeGen 生成 <code>OmniPilot.xcodeproj</code>。</li>
</ul>
<p>把逻辑放进 Swift Package 的一个直接收益是可以独立测试。例如剪贴板历史的去重、条数限制、持久化和清空行为，都可以通过 <code>swift test</code> 验证，不需要启动菜单栏界面。</p>
<h2>构建和试用</h2>
<p>项目面向 Apple Silicon，最低支持 macOS 13。安装 Xcode 和 XcodeGen 后，可以这样构建：</p>
<pre><code class="language-sh">brew install xcodegen
Scripts/build-app.sh
open build/OmniPilot.app</code></pre>
<p>运行核心逻辑测试：</p>
<pre><code class="language-sh">cd Packages/OmniPilotKit
swift test</code></pre>
<p>应用日志写入 <code>~/Library/Logs/OmniPilot/omnipilot.log</code>。打包脚本支持生成 zip 或 dmg；更完整的签名、公证和 Gatekeeper 说明可以查看 <code>Docs/distribution.md</code>。</p>
<h2>一个只为自己服务的小工具</h2>
<p>OmniPilot 的方向不是“菜单栏里还能塞什么”，而是“我自己的工作流里，有什么值得放到这里”。它不试图替代其他成熟，常见的菜单栏应用，也不追求功能数量；它只把 SSH 隧道和剪贴板历史这类我确实需要的东西，放到一个随手可用的位置。</p>
<p>后续要不要增加新功能，我也会沿用同一个标准：只有它能解决我自己的真实麻烦，且具备个性化的需求，才值得进入 OmniPilot。</p>
<h2>项目地址</h2>
<ul>
<li>GitHub：<a href="https://github.com/drofyn/omnipilot">https://github.com/drofyn/omnipilot</a></li>
</ul>]]></description>
    <pubDate>Fri, 16 Oct 2026 12:21:35 +0800</pubDate>
    <dc:creator>drofyn</dc:creator>
    <guid>https://writecho.com/软件开发/6.html</guid>
</item>
<item>
    <title>sing-box:大陆服务器上的透明分流利器</title>
    <link>https://writecho.com/服务器/4.html</link>
    <description><![CDATA[<h2>问题:大陆服务器上的"防火墙"</h2>
<p>大陆机房服务器的日常痛点,每个运维都懂:</p>
<ul>
<li><code>git clone</code> GitHub 仓库,克隆到一半断连、超时</li>
<li><code>docker pull</code> 官方镜像仓库,进度条卡死</li>
<li><code>pip install</code> / <code>npm install</code> 海外源,慢如蜗牛</li>
<li>想连 Google 系 API,直接不可达</li>
<li>更隐蔽的:DNS 污染/劫持,域名解析到错误 IP,业务流量被"换包"</li>
</ul>
<h2>常规方案为什么不彻底</h2>
<p>最常见的做法是 <code>http_proxy</code> / <code>socks5</code> 环境变量:</p>
<pre><code class="language-bash">export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890
</code></pre>
<p>它有几个硬伤:</p>
<ul>
<li><strong>只对显式支持代理协议的程序生效</strong>。<code>docker daemon</code> 要单独配 <code>proxy</code> 配置、<code>git</code> 要单独配、很多 CLI 工具根本不认环境变量,漏一个程序就裸连被墙</li>
<li><strong>环境变量是全局侵入</strong>。<code>curl</code> 一个内网地址也会先走代理,日志里到处都是代理 IP,还可能误伤内网流量</li>
<li><strong>DNS 不处理</strong>。环境变量代理只代理 TCP 数据,域名解析还是本地 DNS 做——污染/劫持问题依旧存在</li>
<li><strong>按进程粒度配置</strong>。服务器上跑着几十个进程,不可能每个都去配</li>
</ul>
<h2>sing-box:TUN 透明分流</h2>
<p>sing-box 的思路完全不同:它创建一张<strong>虚拟网卡(TUN)</strong>,在内核层面接管整个系统的流量。所有应用的流量先进这张虚拟网卡,进入用户态后由 sing-box 按规则路由,应用完全无感知——不配任何环境变量,所有程序(包括 docker daemon、包管理器、任意 CLI)自动被分流。</p>
<p>配合<strong>DNS 分流</strong>和<strong>规则集</strong>,可以做到:</p>
<ul>
<li>国内域名(geosite-cn)→ 大陆 DNS(如 223.5.5.5)解析,大陆出口直连</li>
<li>海外域名 → 海外 DNS(如 8.8.8.8,通过代理链路转发)解析,从源头杜绝污染</li>
<li>流量按 geosite/geoip 规则自动分流,无需逐进程配置</li>
</ul>
<h2>单机双 IP + 远端代理的典型架构</h2>
<p>很多大陆服务器有<strong>双出口</strong>(一条大陆线路、一条香港/海外线路),或者有一台海外 VPS 做代理。sing-box 可以把这两者组合成一套完整的分流体系:</p>
<pre><code class="language-json">&quot;outbounds&quot;: [
  { &quot;type&quot;: &quot;direct&quot;, &quot;tag&quot;: &quot;direct-mainland&quot;, &quot;bind_interface&quot;: &quot;eth2&quot; },
  { &quot;type&quot;: &quot;direct&quot;, &quot;tag&quot;: &quot;direct-hongkong&quot;, &quot;bind_interface&quot;: &quot;eth1&quot; },
  {
    &quot;type&quot;: &quot;vless&quot;,
    &quot;tag&quot;: &quot;proxy-hk&quot;,
    &quot;server&quot;: &quot;你的香港VPS地址&quot;,
    &quot;server_port&quot;: 443,
    &quot;uuid&quot;: &quot;...&quot;,
    &quot;tls&quot;: { &quot;enabled&quot;: true }
  }
],
&quot;route&quot;: {
  &quot;rules&quot;: [
    { &quot;rule_set&quot;: [&quot;geoip-cn&quot;, &quot;geosite-cn&quot;], &quot;action&quot;: &quot;route&quot;, &quot;outbound&quot;: &quot;direct-mainland&quot; }
  ],
  &quot;final&quot;: &quot;proxy-hk&quot;
}
</code></pre>
<p>要点:</p>
<ul>
<li><strong>国内流量</strong>:geoip-cn / geosite-cn 命中 → <code>direct-mainland</code> 直连,延迟最低,不浪费代理带宽</li>
<li><strong>海外流量</strong>:未命中规则 → <code>final: proxy-hk</code>,走香港 VPS 的 vless/trojan 隧道,彻底翻墙</li>
<li><strong>DNS</strong>:国内域名走大陆 DNS,海外域名走香港 DNS(detour 到代理链路),双保险防污染</li>
<li>单机只有一根出口线的场景也一样:把 <code>direct-mainland</code> 换成默认直连,海外流量全部走代理即可</li>
</ul>
<h2>为什么比 tproxy 更现代</h2>
<p>Linux 上做透明代理,老派方案是 <code>tproxy</code> + iptables/nftables。tproxy 本身没错,但用它搭一套完整分流要面对:</p>
<ul>
<li>iptables/nftables 规则繁琐,<code>REDIRECT</code> vs <code>TPROXY</code> 的区别、IPv4/IPv6 双栈规则、UDP 处理,边界情况极多</li>
<li>DNS 劫持要自己用 dnsmasq/iptables 拼装</li>
<li>规则更新(geoip 数据库)要自己下载、转换、重载 iptables</li>
<li>换一台机器就要重新调一遍,可迁移性差</li>
</ul>
<p>sing-box 把这些全部收敛进<strong>一个配置文件</strong>:路由规则、DNS 分流、出站选择都是声明式配置,规则集(geosite/geoip)自动从远端更新,配置即代码,迁移成本几乎为零。它还自带 sniff(流量探测)、直连/代理健康检查、负载均衡等能力,生态比 tproxy 时代完整得多。</p>
<h2>小结</h2>
<p>在无法直接访问 GitHub、Docker Registry、Google 的大陆服务器上,sing-box 用一张虚拟网卡 + 声明式规则,把"国内直连、海外代理"的分流做到了系统级透明:不需要为每个程序配代理,不污染环境变量,不依赖脆弱的 DNS,配置可版本化、可迁移。相比 http_proxy/socks5 的"半吊子代理"和 tproxy 的"手工拼装",它是更彻底、更现代、更好维护的答案。</p>]]></description>
    <pubDate>Sat, 10 Oct 2026 17:57:30 +0800</pubDate>
    <dc:creator>drofyn</dc:creator>
    <guid>https://writecho.com/服务器/4.html</guid>
</item>
<item>
    <title>把路由器变成局域网协作小站:Nook 的开发笔记</title>
    <link>https://writecho.com/路由器/3.html</link>
    <description><![CDATA[<blockquote>
<p>一个跑在 OpenWrt 路由器上的零依赖局域网协作服务——共享剪贴板、文件公告板、WebRTC 点对点传文件,以及配套的 LuCI 管理界面。</p>
</blockquote>
<h2>为什么做这个</h2>
<p>跨设备传文件、同步一段文字或链接,是家庭、办公室、宿舍里最高频也最碎片化的需求。但现有的方案总有让人不舒服的地方:</p>
<ul>
<li><strong>要登录第三方账号</strong>,隐私和依赖都交出去了;</li>
<li><strong>大文件走公网云中转</strong>,速度受限、流量有成本,还可能在途中被看;</li>
<li><strong>没公网或弱公网环境直接不可用</strong>;</li>
<li><strong>每台设备都得装客户端</strong>,手机、电脑、平板、同事的机器……装一遍就劝退一半人。</li>
</ul>
<p>而每个家庭/办公室几乎都有一台 7×24 小时开机的东西——<strong>路由器</strong>。它天然就在局域网的中心,天然常驻,天然被所有设备信任。那为什么不能把路由器本身变成一个"随时可用的本地共享空间"?</p>
<p>这就是 Nook 的出发点:</p>
<ul>
<li><strong>零客户端</strong>——浏览器打开即用,不用装任何 App;</li>
<li><strong>零公网依赖</strong>——不需要外部服务器、云账号、公网链路,断网也能用;</li>
<li><strong>本地优先</strong>——文件内容不落路由器磁盘,尽量在设备之间点对点直传;</li>
<li><strong>路由器即入口</strong>——开机常驻,永远在同一个地址等你。</li>
</ul>
<h2>项目形态</h2>
<p>Nook 包含两个仓库:</p>
<ol>
<li><strong>nook</strong>——Go 编写的核心服务,跑在 OpenWrt/ImmortalWrt 上,提供 Web 页面 + WebSocket 信令 + 内存态协调(Hub)。</li>
<li><strong>luci-app-nook</strong>——配套的 LuCI 应用,在 OpenWrt 管理界面 <strong>Services → Nook</strong> 里查看服务状态、启停服务、配置监听地址。</li>
</ol>
<p>两个包都是标准 OpenWrt 打包(nFPM 生成 ipk / apk),覆盖 aarch64_cortex-a53 和 x86_64 两种架构,CI 在打 <code>v</code> 开头 tag 时自动构建发布。</p>
<h2>能做什么</h2>
<p>打开 <code>http://路由器地址:8088</code> 之后:</p>
<ul>
<li><strong>局域网设备自动发现</strong>——所有打开页面的设备实时出现在在线列表里,离线自动移除;</li>
<li><strong>共享剪贴板</strong>——发布一段文本/链接,局域网内所有设备实时同步;自动识别 URL 可点击;每条支持复制,还带系统剪贴板 Paste;</li>
<li><strong>文件公告板</strong>——把文件"发布"到公告板,其他设备点一下就能领取;支持多文件、单条撤回、一键清空自己的公告;</li>
<li><strong>直接发送</strong>——在设备列表里点某个设备,直接给它发文本或文件,对方弹窗接受/拒绝;</li>
<li><strong>设备命名</strong>——自定义设备名,可记忆(刷新后保留),可一键重置回自动生成的默认名(按 UA 生成 + 随机后缀)。</li>
</ul>
<h2>关键设计:文件内容不经过路由器</h2>
<p>这是 Nook 最核心的设计决策:<strong>默认不把文件存到路由器上</strong>。</p>
<p>文件公告板只在路由器内存里保存文件的<strong>元信息</strong>(文件名、大小、MIME、发布者、发布时间)。真正的文件内容始终留在发布者的浏览器里;其他设备点击领取时,通过 <strong>WebRTC DataChannel</strong> 在设备之间点对点传输,路由器只做信令中转。</p>
<p>对应的取舍:</p>
<ul>
<li>发布者的页面需要保持打开、设备保持在线;</li>
<li>发布者刷新页面或关浏览器,相关公告自动失效;</li>
<li>路由器不持久保存文件内容——低端路由器的存储和 CPU 都不会被大文件拖垮。</li>
</ul>
<h2>架构</h2>
<pre><code>路由器 (OpenWrt)
┌──────────────────────────────────────┐
│  /usr/bin/nook  (单二进制, 前端已嵌入)  │
│   - HTTP: /  /ws  /config             │
│   - Hub: 内存态 (peers/clipboard/offers)│
└──────────┬───────────────┬────────────┘
   WebSocket (信令/元信息)    HTTP (页面)
           │                │
     ┌─────▼─────┐    ┌─────▼─────┐
     │ 设备 A     │    │ 设备 B     │
     │ 浏览器     │◄──►│ 浏览器     │
     └───────────┘WebRTC└───────────┘
        文件内容 P2P 直传, 不经过路由器
</code></pre>
<p>技术栈刻意保持极简:</p>
<ul>
<li><strong>后端</strong>:Go 标准库 + <code>gorilla/websocket</code> + <code>google/uuid</code>,全部代码约 600 行(不含前端);</li>
<li><strong>前端</strong>:纯 HTML/CSS/JS,零第三方依赖,通过 <code>embed.FS</code> 打进单二进制——部署就是一个文件 + init.d 脚本 + UCI 配置;</li>
<li><strong>宿主集成</strong>:procd 管理、<code>respawn</code> 自动重启、<code>/etc/config/nook</code> 标准 UCI 配置。</li>
</ul>
<h3>WebSocket 协议</h3>
<p>设备通过 <code>/ws</code> 注册,消息是 JSON,核心类型:</p>
<ul>
<li><code>hello</code> / <code>welcome</code>——注册与全量状态下发(自身 ID、在线设备、剪贴板、文件公告);</li>
<li><code>peer-joined</code> / <code>peer-left</code>——上下线广播;</li>
<li><code>clipboard-add</code> / <code>clipboard-added</code>——剪贴板发布与广播;</li>
<li><code>file-board-add</code> / <code>file-board-added</code> / <code>file-board-remove</code> / <code>file-board-removed</code>——公告板增删;</li>
<li><code>file-board-request</code> / <code>file-board-unavailable</code>——领取请求与失败;</li>
<li><code>signal</code>——WebRTC offer/answer/ICE candidate 的定向转发。</li>
</ul>
<h3>文件传输协议(DataChannel 内)</h3>
<p>发送方先发 <code>file-offer</code>(文件名/大小/MIME),接收方回 <code>file-accept</code> 或 <code>file-reject</code>,然后按 <strong>64 KB 分块</strong>流式发送二进制,最后 <code>file-done</code> 收尾。状态机:<code>waiting → accepted → sending → done</code>,或 <code>rejected / error</code>。公告板领取和直接发送共用这套机制。</p>
<h3>一些工程细节</h3>
<ul>
<li>WebSocket 升级时做 <strong>Origin 校验</strong>,防跨站 WebSocket 劫持;</li>
<li>心跳 Ping 30 秒,读超时 60 秒,写超时 10 秒,空闲连接不被误杀;</li>
<li>单条消息上限 64 KB;剪贴板最多 50 条、单条 64 KB;公告板最多 100 条;</li>
<li>优雅关闭:收到 SIGINT/SIGTERM 后 5 秒内收尾;</li>
<li>设备 ID 用 UUID 前 8 位,不可预测;</li>
<li>前端断线 3 秒自动重连,重连后重新注册拉全量状态。</li>
</ul>
<h2>配套的 LuCI 界面</h2>
<p>luci-app-nook 让 Nook 融入 OpenWrt 原生管理体验:</p>
<ul>
<li>后端是 <strong>ucode</strong> 写的 rpcd 插件(<code>luci.nook</code>),暴露 <code>get_status</code> 和 <code>service_action</code> 两个方法;</li>
<li>状态页显示 <strong>running</strong>(运行中)和 <strong>enabled</strong>(开机自启)两个状态,一键 start/stop/restart/enable/disable;</li>
<li>配置页读写 <code>/etc/config/nook</code> 的启用开关和监听地址,改配置自动触发服务重载;</li>
<li>同时兼容 LuCI 23.05 和 24.10(通知 API 做了降级);</li>
<li>权限走标准 ACL,最小授权:读状态 + 写 nook 配置 + 执行 init.d 脚本。</li>
</ul>
<h2>安装与使用</h2>
<pre><code class="language-sh"># aarch64 / x86_64 按架构选包
opkg install ./nook_0.1.6-1_aarch64_cortex-a53.ipk
# 或 apk 包管理器(未签名本地包)
apk add --allow-untrusted ./nook_0.1.6-r1_aarch64_cortex-a53.apk

/etc/init.d/nook enable
/etc/init.d/nook start
</code></pre>
<p>然后同一局域网内访问 <code>http://192.168.1.1:8088</code> 即可。升级行为也考虑过:首次安装不自动启动;升级时若服务正在运行则自动重启,未运行则不打扰。</p>
<h2>设计取舍与边界</h2>
<p>Nook 明确<strong>不做</strong>的事,和做了一样重要:</p>
<ul>
<li><strong>不做公网/跨网传输</strong>——只面向同一局域网;</li>
<li><strong>不做账号体系</strong>——没有登录、没有云同步;</li>
<li><strong>不做持久化</strong>——剪贴板、在线设备、公告都是内存态,服务重启即清空(已接受的取舍);</li>
<li><strong>不做认证加密</strong>——当前版本基于"可信局域网"假设;没有端到端加密,局域网内流量可被嗅探。这一点在文档里写得很清楚,后续版本会评估设备配对与传输加密。</li>
</ul>
<h2>小结</h2>
<p>Nook 解决的是一个小而真实的问题:在局域网里,设备之间应该能像在同一个房间一样自然地交换东西。它把"路由器"这个每家都有的基础设施变成了协作入口,用浏览器消灭客户端,用 WebRTC 让文件内容绕开路由器,用最克制的技术栈(Go + 原生前端 + 标准 OpenWrt 打包)换来低端路由器上也能跑的轻量体验。</p>
<p>代码在 GitHub,欢迎围观和提 issue:</p>
<ul>
<li><a href="https://github.com/drofyn/nook">drofyn/nook</a></li>
<li><a href="https://github.com/drofyn/luci-app-nook">drofyn/luci-app-nook</a></li>
</ul>
<hr />]]></description>
    <pubDate>Thu, 17 Sep 2026 11:39:35 +0800</pubDate>
    <dc:creator>drofyn</dc:creator>
    <guid>https://writecho.com/路由器/3.html</guid>
</item>
</channel>
</rss>