Releases: igevin/mHost
Release list
mHost v0.3.3
v0.3.3 更新说明
本期相对 v0.3.2 主要是一次安全加固 + System Hosts 体验优化,并附带了一轮发版/版本号文档的修正。
🔒 安全修复:escapeHtml 加固(#110,#117)
RuleEditor.tsx 和 SystemHosts.tsx 各自带了一份相同的 escapeHtml,只转义 &、<、> — 文本节点够用,但任何后续把值塞进属性插值(href、title、onclick)就构成 stored XSS。这次:
- 合并到单一
src/lib/escape.ts - 顺手把
"和'也补上(顺序仍是&在前,避免&被二次编码) - 12 个新单测覆盖五个字符 + 双重编码回归 +
<script>和属性逃逸中和
✨ 新增:System Hosts 行号 gutter(#111,#118)
System Hosts 页面现在常驻显示左侧 44px 行号栏,1-based。开启搜索后,当前活动匹配对应的行号加粗 + 主色高亮。原本搜索栏右侧的 "Line N of M" 小标签下线,gutter 成为主要的"我在第几行"affordance。
实现要点:
- 布局沿用
RuleEditor的同款 wrapper + 绝对定位 gutter,保持视觉一致 - gutter
overflow: hidden,通过onScroll把<pre>.scrollTop同步过去,无独立滚动条 lineNumbersmemo 仅依赖totalLines,搜索打字不会重建数组
📖 文档(无运行时变更,#119)
doc/build-and-version.md 重新梳理:
- 画清楚
__APP_VERSION__同时驱动 About 卡片 和 检查更新 的链路 - 修正 CI 矩阵表中第二个 runner 被误标为 "Intel" 的描述(两个 runner 都是
macos-latest) - 列出全部 6 个
Cargo.toml路径,明确说"不要碰"
下载
| 平台 | DMG |
|---|---|
| Apple Silicon | mHost_0.3.3_aarch64.dmg |
| Intel | mHost_0.3.3_x64.dmg |
.app.tar.gz 同帖提供(mHost_aarch64.app.tar.gz、mHost_x64.app.tar.gz)。
升级方式
- 已安装 v0.3.x:打开 mHost → Settings → "检查更新" → 看到 v0.3.3 后点击跳转下载页
- 首次安装或从 v0.2.x 升级:直接下载对应架构的 DMG 拖入 Applications 即可,不会影响
/etc/hosts中# ---- mHost start ----managed block
已知问题
- 安装包未做 Apple 代码签名(同 v0.3.x 一直的状态),首次打开如遇 Gatekeeper 拦截请右键 → 打开
- Linux / Windows 不在当前发版范围(#67)
mHost v0.3.2
See the assets to download this version.
mHost v0.3.1
mHost v0.3.1
DNS 多 Profile 支持(issue #67 / PR #99)+ 8 次 app-exit DNS 清理修复终于打通 Cmd-Q 路径。
安装包见下方 Assets。Apple Silicon →
mHost_0.3.1_aarch64.dmg;Intel Mac →mHost_0.3.1_x64.dmg。
Highlights
1. DNS 多 Profile(PR #99 / issue #67)
DNS mode 现在支持多 profile,类比 Hosts mode 的 profile 切换体验。包含 UI redesign、RuleEditor 集成、确定性 conflict 排序,加上 4 轮 follow-up fix。
- 多 profile 同时启用:disable 一个 profile 不再 reload DNS 规则,避免 disable 其他 profile 的副作用(dual-file 持久化 + no-reload-on-disable)
- 确定性 conflict 排序:rule 冲突时按稳定顺序处理,行为可预测
- Profile-level 开关 UI:每个 profile 有独立的 enable/disable
- Edit / Edit Info 双按钮:header 同时保留 Edit(→ 规则编辑器)和 Edit Info(→ name/description/tags)两个独立按钮
- Review follow-ups:clippy 警告清理、duplicate mode 守卫、save_profile crash safety
PR #99 = 1 个 feature commit + 4 轮 follow-up(UX 4 bugs、manual-testing 3 bugs、union-not-reloading、review clippy/safety)
2. App-exit DNS 清理修复(8 commits,最终走通 Cmd-Q)
v0.3.0 在 enable DNS mode 之后,通过 tray Quit / Cmd-Q / Ctrl+C 三条路径退出时,DNS 不一定能 restore 回原始状态。v0.3.1 经过 8 次迭代终于打通。
- 同步 disable DNS mode 再退出:在 ExitRequested 之前同步 disable,避免 cleanup 跑在 async runtime 关闭之后
- 三路径统一用
interactive=true:让 proxy 自愈超时分支在 privileged proxy 死亡时弹 sudo - Cmd-Q 走 SIGTERM handler:让 Path A 和 Path B 复用同一套 async 路径
- Path A 走 dedicated
std::thread:避免 tao 主线程 + tokio runtime race(之前block_on在 ExitRequested 上下文里死锁) - 保留 Regular activation policy:Accessory 模式下 Cmd-Q 不触发 Tauri 的 ExitRequested,所以先回到 Regular(UX 妥协:会保留 Dock icon;issue #100 后续用 objc2 delegate 重新关闭 Dock)
- objc2 NSApplicationDelegate override:runtime 装一个
MhostQuitHandlerDelegate,覆盖applicationShouldTerminate:在 OS 层拦截 Cmd-Q,同步跑 DNS cleanup 后返回NSTerminateNow(这才是 OS 级 Cmd-Q 的真正出路) Box::leak存 stable AppHandle 指针:之前用&local_clone会在 setup 返回后变 dangling pointer 导致 SIGSEGV- Box cast 修正:原始
&Box<AppHandle> as *const _ as *mut ()错把 Arc 指针当 Box 指针,dereference 时触发misaligned_pointer_dereferencepanic;改成直接Box::leak(Box::new(app.handle().clone()))存 AppHandle 本身
成功路径:tray Quit / Cmd-Q / SIGINT / SIGTERM 全部走 cleanup_dns_on_exit(state, interactive=true),幂等(dns_enabled=false 是 no-op)。失败兜底:400ms watchdog 强杀。
3. DNS LRU response cache 在 reload_rules 时失效
reload_rules 之后继续返回 stale query 响应(hit cache 返回了过期的上游 IP)。改成 reload 时清空 LRU cache。
4. CLAUDE.md 更新 Exit/DNS cleanup 契约
记录 Cmd-Q 在 Tauri 2 on macOS 上 不 触发 RunEvent::ExitRequested(tao 直接返回 NSTerminateNow)。所有新加 exit 路径必须 hook OS 级事件。
Files changed
PR #99(DNS multi-profile)
crates/mhost-storage/src/manifest.rs— DnsProfile 持久化 + dual-file 拆分crates/mhost-dns/src/{config,server,platform}.rs— multi-profile 支持src/components/DnsProfileView.tsx— Edit 按钮 + RuleEditorsrc/components/RuleEditor.tsx— DNS rule editor UIsrc/stores/dns.ts— multi-profile state- 4 轮 follow-up commits:review clippy、duplicate 守卫、save_profile crash safety、union-not-reloading、UX 4 bugs、manual-testing 3 bugs
App-exit cleanup
src-tauri/src/lib.rs— Path A: dedicatedstd::thread+Box::leak存 AppHandle + 修复 Box castsrc-tauri/src/platform/macos.rs—install_quit_handler用 objc2ClassBuilder装MhostQuitHandlerDelegatesrc-tauri/src/tray.rs— 同步调用cleanup_dns_on_exitsrc-tauri/src/commands/dns.rs—cleanup_dns_on_exit幂等- 4 处 cleanup 路径(tray Quit / Cmd-Q / SIGINT / SIGTERM)统一
interactive=true
其他
crates/mhost-dns/src/server.rs— LRU cache invalidate on reload_rulesCLAUDE.md— 退出契约文档
数字
- Rust: 354 测试通过(v0.3.0 的 339 + 15 新增)
- 前端: 194 测试通过(v0.3.0 的 166 + 28 新增)
cargo fmt --checkcleancargo clippy --all-targets --all-features -- -D warningscleanpnpm buildcleancargo build --releaseclean- CI: macos-latest(aarch64 + x86_64 matrix build)通过
Migration notes
升级到 v0.3.1:
- 数据:DNS mode 多 profile 持久化格式向前兼容 v0.3.0;升级后第一次 enable DNS mode 时会按新 dual-file schema 重写 manifest
- 不需要停用 DNS mode:升级过程中 DNS server 会自动重启;如有强需求,disable 再升级也安全
mHost v0.3.0
mHost v0.3.0
DNS mode hardening (capture/restore + mid-session upstream refresh) and DNS profile detail page UI fixes.
安装包见下方 Assets。Apple Silicon →
mHost_0.3.0_aarch64.dmg;Intel Mac →mHost_0.3.0_x64.dmg。
Highlights
1. DNS capture/restore 按用户原始配置分流(PR #96)
之前 enable DNS mode 时,三层 fallback (networksetup → ipconfig → 公共 DNS) 合并返回成一个 Vec<String> 丢掉了「用户是否手动配过」这个信号。后果:在家 WiFi(DHCP-only,没动过 DNS)enable DNS mode 之后,到公司 WiFi disable 时,会把家里的 DHCP 推的 IP 错误地写回到公司 DNS 上。
现在把 capture 拆成两部分:
capture_dns_state() → OriginalDns- Tier 1 (
networksetup -getdnsservers <iface>) 非空 →Manual(servers):用户在 System Settings 里手动配的 - 其余情况 →
DhcpEmpty:用户没手动配(DHCP default / 真的空)
- Tier 1 (
get_upstream_resolvers() → Vec<String>- 完整 fallback chain(含公共兜底),仅供 DNS server 转发用
Manual snapshot 的 restore 行为不变(写回原 IP 列表);DhcpEmpty 的 restore 写 Empty(DHCP default),避免跨网切换时泄漏上次抓的 DHCP 推 IP。
2. Mid-session 上游 DNS 自动刷新(PR #96)
- Manual snapshot → 上游 freeze 在 enable 时刻的那组 IP(用户自己的设定,不替他做主)
- DhcpEmpty snapshot → DnsServer 启动后台 task,每 60s 调一次
get_upstream_resolvers(),对比当前上游,变化才 hot-swap resolver(parking_lot::RwLock<Arc<TokioAsyncResolver>>);stop()通过tokio::sync::Notify让后台 task 立即退出
用户场景:enable DNS mode 后长期不 disable,但中途切换 WiFi 忘记——上游会自动跟上新网络。
3. DNS 详情页补回 RuleEditor 和 Edit 按钮(PR #97)
PR #92(Phase 2)拆分 ProfileView 的时候,DnsProfileView 缺了一块:
- header 缺独立的「Edit」(→ 规则编辑器)按钮
- main 缺
<RuleEditor>渲染区
(v0.2 早期注释说「编辑走独立 dialog」,但 dialog 从未实现。)
这次镜像 HostsProfileView 集成规则编辑器,header 同时保留 Edit(→ 规则)和 Edit Info(→ name/description/tags)两个独立按钮。
4. ACTIVE 状态条改回 sidebar 底部(PR #97)
之前 <StatusBar /> 在 Layout.tsx 被渲染成 display: flex 行布局的第三个 column——但 CSS class 名字叫 .sidebarFooter,语义上是 sidebar 底部。这次把它从 Layout 移进 Sidebar 内部,用 sidebarSpacer { flex: 1 } 推到 sidebar 底部。
5. Manifest migration(向后兼容)
Manifest.original_dns 从 Option<Vec<String>> 改成 Option<OriginalDns>。自定义 Deserialize 兼容五种磁盘格式:
| 磁盘格式 | 解码为 |
|---|---|
"original_dns": null / 字段缺失 |
None |
"original_dns": [] |
Some(DhcpEmpty) |
"original_dns": ["Empty"] |
Some(DhcpEmpty) (v2.0 占位符) |
"original_dns": ["1.1.1.1"] |
Some(Manual(["1.1.1.1"])) |
{"kind":"manual","servers":[...]} (新格式) |
Some(Manual(vec)) |
{"kind":"dhcp_empty"} (新格式) |
Some(DhcpEmpty) |
无强升迁;下次 enable 时 capture_dns_state 看到 Tier 1 空就会把 stale vec 改成 DhcpEmpty,self-heals。
Files changed (cumulative for v0.3.0)
PR #96(Rust)
crates/mhost-core/src/models.rs— 新OriginalDnstagged enum + 自定义 serde + 9 个测试crates/mhost-dns/src/platform.rs—get_system_dns拆成capture_dns_state+get_upstream_resolvers;enable/disable_dns_mode/osascript_restore签名改&OriginalDnscrates/mhost-dns/src/server.rs— DnsConfig 加refresh_upstream;DnsServer resolver 改parking_lot::RwLock<Arc<...>>;新增run_upstream_refresh_loop后台 taskcrates/mhost-dns/src/config.rs— DnsConfig 新字段crates/mhost-storage/src/manifest.rs—Option<OriginalDns>+ migration 测试src/state/mod.rs—Mutex<OriginalDns>+try_recover_dns重写src/commands/dns.rs—set_dns_mode_enable用capture_dns_state,按 variant 设refresh_upstream
PR #96(前端)
src/types/index.ts—OriginalDnsdiscriminated unionsrc/pages/Settings.tsx— 三种状态渲染分支src/pages/__tests__/Settings.test.tsx— Settings fixturessrc/components/__tests__/Layout.test.tsx— fixture
PR #97(前端)
src/components/DnsProfileView.tsx— 集成<RuleEditor>+ Edit 按钮(保留 Edit Info)src/components/Layout.tsx— 移除<StatusBar />src/components/Sidebar.tsx— 在sidebarSpacer后挂<StatusBar />src/pages/__tests__/ProfileView.test.tsx— DNS-mode 新增 6 个规则编辑测试
数字
- Rust: 339 测试通过(含 9 + 5 + 3 个新增)
- 前端: 166 测试通过(含 6 个新增 DNS 模式测试)
cargo fmt --checkcleancargo clippy --all-targets --all-features -- -D warningscleanpnpm buildcleancargo build --releaseclean- CI: macos-latest(aarch64 + x86_64 matrix build)通过
Migration notes
升级到 v0.3.0:
- 数据:
Manifest.original_dns自动按上面 migration 表兼容旧格式,无强升迁。 - 网络行为变化:之前 enable 的 DNS mode 在 v0.3.0 第一次重启时会重新跑
capture_dns_state:- 如果你在 System Settings 里配过 DNS → 沿用为
Manual,行为不变 - 如果之前是 DHCP/empty → 升级为
DhcpEmpty,本次 session 内不会改变 networksetup,且 disable 会写回Empty
- 如果你在 System Settings 里配过 DNS → 沿用为
- 不需要停用 DNS mode:升级过程中 DNS server 会自动重启;如有强需求,disable 再升级也安全。
mHost v0.2.1
What's New
ApplyConfirmDialog with Diff Preview (#59)
应用 hosts 前增加 diff 预览确认对话框,避免误操作覆盖系统 hosts。
- sidebar、ManagementDrawer、ProfileList 中的 Enable/Disable 开关统一接入预览流程
- 全局错误提示从各组件移至 Layout 统一渲染
- 对话框组件统一使用
createPortal渲染到document.body
重复规则检测 (#61)
同一 profile 内自动检测重复规则,防止冲突配置被应用到系统。
- SameIp:相同 IP 对应不同域名,提示警告(黄色边框)
- DifferentIp:相同域名对应不同 IP,阻断保存(红色边框)
- RuleEditor 和 ImportDialog 均支持检测和提示
Search & Replace in RuleEditor (#62)
规则编辑器集成查找替换功能,提升批量编辑效率。
Cmd+F呼出搜索栏,Esc关闭- 支持
Enter/Shift+Enter切换匹配项 - 支持 Replace / Replace All
Configuration Snapshots (#64)
引入配置快照机制,支持 hosts 配置的版本管理和回滚。
- 新增 Tauri 命令:
save_snapshot、list_snapshots、load_snapshot、delete_snapshot - apply / enable_and_apply 后自动创建快照(列表为空,或最新快照超过 3 天)
- 独立
/snapshot页面管理所有快照
UI 调整 (#65)
优化 Settings 页面和规则编辑器布局。
- Settings 移除 Hosts Management rollback(功能已迁移至 ApplyConfirmDialog)
- System Hosts Preview 拆分为独立
/hosts页面,独占整页高度 - RuleEditor 左侧新增行号栏,同步滚动
Bug Fixes
- WebView 点击被吞没:ProfileView Save/Cancel 按钮添加
onPointerDown,修复 Tauri WKWebView 中输入后点击失效的问题 (#60)
CI/CD
- 升级 Node.js 20 → 22、pnpm 9 → 10,修复 GitHub Actions 弃用警告
- 清理未使用的导入,确保 TypeScript 编译通过
Downloads
| 文件 | 说明 |
|---|---|
mHost_0.2.1_aarch64.dmg |
macOS Apple Silicon (ARM64) 安装包 |
mHost_0.2.1_x64.dmg |
macOS Intel (x86_64) 安装包 |
mHost_aarch64.app.tar.gz |
macOS Apple Silicon 应用归档 |
mHost_x64.app.tar.gz |
macOS Intel 应用归档 |
mHost v0.2.0
What's New
UI Redesign — Sidebar + Management Drawer Layout (#55)
全新的界面布局,采用侧边栏 + 管理抽屉的设计,提供更清晰的操作体验。
Bug Fixes
- 对话框点击失效问题:修复 Tauri WKWebView 中在输入框输入后点击按钮需要两次点击的问题,通过 pointerdown 事件绑定解决 WebKit 焦点转移吞噬点击的兼容性问题
- CreateProfileDialog 无法弹出:修复侧边栏 overflow:hidden 导致创建对话框被裁剪的问题,改用 createPortal 渲染到 document.body
- 空状态下无法创建 Profile:修复无 Profile 时点击「+ New Profile」无响应的问题
- 重复点击防护:为所有操作按钮(Create、Duplicate、Delete、Save 等)添加 loading 状态锁,防止异步操作期间的重复点击
- 对话框组件统一使用 Portal:CreateProfileDialog、ManagementDrawer、ImportDialog 统一使用 createPortal 渲染
Improvements
- WebKit 兼容性优化:提取 WebKit pointerdown 修复为共享 hook (
useWebKitPointerDown),供所有对话框复用 - 权限精细化:编辑 Profile 的名称/描述/标签不再要求管理员权限,仅修改规则或启用状态时才需要
CI/CD
- 升级 Node.js 20 → 22、pnpm 9 → 10,修复 GitHub Actions 弃用警告
- 清理未使用的导入,确保 TypeScript 编译通过
Downloads
| 文件 | 说明 |
|---|---|
mHost_0.2.0_aarch64.dmg |
macOS Apple Silicon (ARM64) 安装包 |
mHost_0.2.0_x64.dmg |
macOS Intel (x86_64) 安装包 |
mHost_aarch64.app.tar.gz |
macOS Apple Silicon 应用归档 |
mHost_x64.app.tar.gz |
macOS Intel 应用归档 |
mHost v0.1.1
See the assets to download this version.
mHost v0.1.0
See the assets to download this version.