<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Magisk on idiotfan</title><link>https://idiotfan.wang/tags/magisk/</link><description>Recent content in Magisk on idiotfan</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Sat, 22 Aug 2026 10:30:00 +0800</lastBuildDate><atom:link href="https://idiotfan.wang/tags/magisk/index.xml" rel="self" type="application/rss+xml"/><item><title>我是怎么把两台红米 Note 13 5G 变成 7×24 家庭服务器的</title><link>https://idiotfan.wang/posts/redmi-note-13-server/</link><pubDate>Sat, 22 Aug 2026 10:30:00 +0800</pubDate><guid>https://idiotfan.wang/posts/redmi-note-13-server/</guid><description>两台红米 Note 13 5G：MTK 硬件方式解锁 bootloader、无 init_boot 分区的特殊 Root、裁掉上百个 HyperOS 冗余包，再搭一条「网易云歌单 → VPS → 手机」的自动同步链路。中间还经历了双机重启卡死事故。</description><content:encoded><![CDATA[<p>手上有两台红米 Note 13 5G（型号 2312DRAABC，内部代号 <code>gold</code>，天玑 6080，8+256，HyperOS / Android 15）。这 SoC 没有 Linux 主线内核支持，刷不了原生 Linux 发行版，但 8G 内存 + UFS 256G + 能一直插着电——拿来当 <strong>7×24 家庭服务器</strong>正好：音乐库离线听、远程可运维、双机互备。</p>
<p>整个折腾历时一周，从解锁 bootloader 到音乐自动同步上线，中间还出了双机重启卡死的事故。这篇把过程和结果完整记一下。</p>
<h2 id="第一步解锁-bootloader最危险的一步">第一步：解锁 bootloader（最危险的一步）</h2>
<p>小米官方解锁等待期长、限制多，走的是 <strong>Jz8Root 的 MTK RPMB 硬件解锁</strong>路线——通过 BROM 用 preloader 直接写 seccfg。但第一原则是<strong>先备份一切</strong>：动任何东西之前，先把所有 NV 分区（nvram / nvdata / nvcfg / persist / protect1 / protect2 / seccfg / lk / RPMB）用 mtkclient 完整导出来。</p>
<blockquote>
<p>nvram/nvdata 丢了就是丢 IMEI 和基带。每台 ~536MB 的备份是砖机或丢 IMEI 时的唯一保险，必须带走。</p>
</blockquote>
<p>进 BROM 有一套完整仪式，其中有一条铁律：<strong>必须用 USB 2.0 口</strong>——USB 3.0 口会静默损坏写入，没有任何报错：</p>
<ol>
<li>手机完全关机</li>
<li>同时按住音量+ 和音量−</li>
<li>插 USB 线，保持按住 15 秒以上，屏幕必须全黑</li>
<li>电脑端出现 <code>0e8d:0003</code></li>
</ol>
<p>电脑侧也有坑：要先停掉 ModemManager（它会抢占 MTK 串口）、卸载 <code>option</code> / <code>usb_wwan</code> 驱动。看到 mtkclient 不认的 <code>0e8d:20ff</code>，基本就是这两个没做干净。</p>
<p>BROM 通了之后，流程其实不复杂：</p>
<pre tabindex="0"><code>scan_lk.py          # → COMPATIBLE_FULL，符合 Path A 硬件解锁条件
da rpmb e ...       # 擦除 RPMB magic（这台定位在 sector 65504）
da seccfg unlock    # 写入解锁
</code></pre><p>两个细节：擦掉 RPMB magic 之后、seccfg unlock 之前<strong>绝不能碰电源键</strong>（preloader 会在任何启动时重写 magic）；解锁后首次开机<strong>必须擦掉 userdata/metadata</strong>，否则直接卡在 dm-verity 的&quot;设备已损坏&quot;界面。<code>fastboot getvar unlocked</code> 返回 <code>yes</code>，两台手机正式离开出厂状态。</p>
<h2 id="第二步root这个机型没有-init_boot">第二步：Root——这个机型没有 init_boot</h2>
<p>这台机器的特殊之处：<strong>没有独立 init_boot 分区，ramdisk 放在 vendor_boot 分区里的 <code>init_boot.cpio</code> 中</strong>。所以 Magisk 标准的&quot;修补 init_boot&quot;流程走不通，得改刷 vendor_boot 补丁镜像，而且<strong>必须 Magisk v30.7 起步</strong>（PR #9370 才加入 vendor_boot 支持），旧版会刷入成功但 root 不生效。</p>
<p>流程：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 1. dump 原厂 vendor_boot（root 前只能走 BROM，root 后直接 dd）</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 2. push 到手机，Magisk app → 安装 → 选择并修补一个文件</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 3. 拉回刷入</span>
</span></span><span class="line"><span class="cl">fastboot flash vendor_boot_b magisk_patched-vendor_boot.img
</span></span></code></pre></td></tr></table>
</div>
</div><p>还有两个坑：</p>
<ul>
<li>本机的提权入口是 <strong><code>/debug_ramdisk/magisk su</code></strong>，<code>/sbin/su</code>、<code>/system/xbin/su</code> 都不存在——别据此误判 root 失效</li>
<li>开机后第一次 <code>su</code> 请求可能被自动拒绝并记住，要到 Magisk 超级用户列表里手动把 <code>shell</code> 改成「允许」</li>
</ul>
<h2 id="第三步裁-hyperos-冗余内存从-78mb-到-5g">第三步：裁 HyperOS 冗余（内存从 78MB 到 5G）</h2>
<p>出厂状态 HyperOS 只给可用内存留了 <strong>78MB</strong>。裁剪之后：</p>
<table>
	<thead>
			<tr>
					<th>指标</th>
					<th style="text-align: right">裁剪前</th>
					<th style="text-align: right">裁剪后（重启稳定）</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Mem used</td>
					<td style="text-align: right">5366 MB</td>
					<td style="text-align: right">2456 MB</td>
			</tr>
			<tr>
					<td>Mem free</td>
					<td style="text-align: right">2212 MB</td>
					<td style="text-align: right"><strong>5122 MB</strong></td>
			</tr>
			<tr>
					<td>Swap used</td>
					<td style="text-align: right">186 MB</td>
					<td style="text-align: right">0 MB</td>
			</tr>
	</tbody>
</table>
<p>具体做了这些：</p>
<ul>
<li><strong>禁用约 100 个系统包</strong>（全部 <code>pm disable-user</code>，可逆）：小爱/AI 全家桶（voiceassist、aiservice、aiasst…）、广告统计（systemAdSolution、analytics…）、应用商店/游戏中心/快应用、云同步/车联/投屏、支付钱包虚拟 SIM、各类测试工具和日志上报</li>
<li><strong>卸载 21 个预装垃圾</strong>（<code>pm uninstall -k --user 0</code>）：淘宝、闲鱼、拼多多、美团、微博、抖音、快手、今日头条、番茄小说、腾讯视频……保留支付宝、高德、WPS、米家</li>
<li><strong>hosts 广告拦截</strong>：Magisk hosts 模块（yhosts 列表 6428 条，含小米系广告域）</li>
<li>关掉负一屏和上滑资讯流（这俩会被系统偷偷重新启用，要重新禁）</li>
</ul>
<p>踩的坑：</p>
<ul>
<li>第一轮<strong>误伤了天气和主题商店</strong>，广告 hosts 又误拦了天气域名。后来恢复 4 个包 + 给 hosts 模块加白名单</li>
<li><code>pm enable</code> 状态落盘有延迟，<strong>启用后立刻重启会丢失</strong>，要等 20 秒再重启</li>
<li><strong>漂移</strong>：重启 5 次后 HyperOS 把 24 个包重新启用了。给 <code>trim-hyperos.sh</code> 加了 <code>verify</code> 模式（只报告不改动）+ KEEP 例外表，以后每次重启后跑一次 verify 自查</li>
</ul>
<h2 id="第四步服务器化">第四步：服务器化</h2>
<p>目标是 7×24 随时能连、能干活：</p>
<ul>
<li><strong>Debian chroot</strong>：chroot-distro 模块装 Debian 12 bookworm arm64（rootfs 只有 74MB），里面跑 sshd:2222 + shellinabox（Web 终端）+ glances（监控面板）</li>
<li><strong>保持唤醒</strong>：写 <code>gold_server</code> wakelock，防熄屏后 deep doze 挂起系统、掐网络</li>
<li><strong>网络调优</strong>：TCP BBR + fq qdisc；CPU 最低频锁定（MTK power HAL 开机后会重置，脚本每 30 秒重写 5 分钟对抗）；GPU 从锁死的 dummy 调度换到 simple_ondemand</li>
<li><strong>WiFi 看门狗</strong>：每 60 秒 ping 网关，连续 5 次失败自动 <code>svc wifi disable/enable</code> 重连——前两天数据链路抽风了 3 次，全部被它自愈</li>
<li><strong>80% 充电上限</strong>：ACC 模块的守护进程在这个机型上不工作（97% 还在充），自己写了脚本直接写 <code>input_suspend</code>：≥80% 停充、≤60% 恢复。长期插电当服务器，电池健康是刚需</li>
</ul>
<h2 id="双机重启卡死事故">双机重启卡死事故</h2>
<p>第二天 13:45，<strong>两台手机重启后同时卡死</strong>：bootanimation 一直转、<code>sys.boot_completed</code> 为空、zygote 状态 restarting、crash buffer 里全是 <code>zygote64 fork system_server</code> 崩溃（<code>/dev/binder</code> 节点缺失）。magiskd 在跑，但 <code>su</code> 不可用。</p>
<p>排查靠排除法：</p>
<ol>
<li>模块逐个全禁 → 能开机</li>
<li>Magisk 版本对比（30.7 / 30.6）→ 都卡</li>
<li>刷回原厂 vendor_boot → 连续 3 次重启正常（证明 ROM 本身稳定，问题在 Magisk 侧）</li>
<li>最终定位：<strong><code>chroot-sshd-autostart.sh</code> 在启动早期把 Debian chroot 挂载进 <code>/debug_ramdisk</code>，与 Magisk preinit 机制冲突</strong> → 第二次重启 preinit 挂载异常 → binder 节点缺失 → zygote 起不来</li>
</ol>
<p>处置：设备 2 降级 Magisk 30.6 + 禁用 chroot 自启脚本，连续多次重启验证通过。</p>
<p>这个事故还顺带挖出一个更隐蔽的地雷：<strong>Magisk 的 <code>service.d</code> 按可执行位运行脚本，不看扩展名</strong>。文档里写的&quot;禁用 = 改名为 <code>.bak</code>&ldquo;其实根本没禁——文件还是 <code>-rwxr-xr-x</code>，只是那台恰好 25 小时没重启没暴露。正确做法：文件移出目录、去掉执行位。</p>
<h2 id="远程运维frp-双隧道">远程运维：FRP 双隧道</h2>
<p>手机在 CGNAT 后面（4G 公网 IP 一天换 4 次），外网直连无解。方案：<strong>手机主动出站连 VPS 上的 frps</strong>（就是「<a href="/posts/my-selfhosted-services/">我的自建服务清单</a>」里那台），每台开两条隧道：</p>
<table>
	<thead>
			<tr>
					<th>设备</th>
					<th>SSH</th>
					<th>adb（保底通道）</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>设备 1</td>
					<td><code>:6012</code></td>
					<td><code>:6014</code></td>
			</tr>
			<tr>
					<td>设备 2</td>
					<td><code>:6008</code></td>
					<td><code>:6009</code></td>
			</tr>
	</tbody>
</table>
<ul>
<li>SSH 仅密钥认证，全世界任何地方一条 <code>ssh gold2</code> 进手机</li>
<li><strong>adb 隧道是保底通道</strong>：即使 Termux 整个被 MIUI 干死，root 层的 adb 依然可进，能修一切。在此之前 Termux 一挂手机就够不着了，只能人肉去插 USB</li>
</ul>
<p>一个有意思的细节：VPS 的 frps 是 0.51.3（ini 配置），手机 frpc 是 0.71.0（toml 配置），<strong>跨 20 个小版本握手正常</strong>。但别为此升级 VPS 的 frps——上面挂着 8 条生产隧道（几家店的 SSH、旁路由、远程桌面），升级风险远大于收益。</p>
<h2 id="音乐自动同步链路">音乐自动同步链路</h2>
<p>手机的主力用途是音乐：<code>/sdcard/Music/音乐U盘</code> 里 682 首 / 6.2GB，Musicolet 离线播。但我想&quot;歌单加歌 → 自动到手机，随时离线听&rdquo;，于是搭了这条链路：</p>
<pre tabindex="0"><code>网易云歌单
   │  systemd timer 每 30 分钟
   ▼
VPS（sync.py：按 song_id 比对线上歌单 ↔ 本地，只下新增）
   │  HTTPS 静态分发 + NATS 通知
   ▼
手机（gold-sync.py --daemon，root 层）
   └ 落盘后触发 MediaStore 重扫 → Musicolet 立即可见
</code></pre><p>几个设计点：</p>
<ul>
<li><strong>NATS 只报信，文件走 HTTP 拉</strong>：NATS 默认 max_payload 1MB，一首歌 8~12MB；VPS 才 1.6G 内存，开 JetStream 不划算。manifest.json 是唯一真相来源，漏收消息不影响最终一致性（还有 30 分钟轮询兜底）</li>
<li><strong>序号对齐重命名</strong>（最值的一段逻辑）：文件名是 <code>NNN. 歌名 - 歌手.mp3</code>，序号 = 歌单位置。我习惯把新歌加在<strong>第一首</strong>，加一首 → 后面所有歌序号 +1 → 全部文件名改变。不对齐的话，下载器会重下整个 156 首的歌单（~1.4GB）；对齐后是 <strong>155 个重命名 + 只下 1 首</strong></li>
<li><strong>冷启动匹配</strong>：手机里已有 676 首、VPS 全新下载序号不同，首次同步要用&quot;去掉 <code>NNN. </code> 前缀的文件名&quot;把本地文件认领回 song_id。且<strong>一个本地文件只能被认领一次</strong>——歌单里有同名同歌手的重复曲目，认领两次就是对同一文件重命名两次直接崩（实际踩过，99 个文件卡在临时名，写了还原逻辑）</li>
</ul>
<p>最隐蔽的坑：</p>
<ul>
<li><strong>MIUI 会切断非前台应用的网络</strong>：Termux 里 <code>curl</code> 全返回 000，连直连 IP 都超时；同一时刻 root 和其他应用全部正常。DNS、WiFi、防火墙全排查了一遍才定位。解法是 <code>termux-wake-lock</code>，开机脚本第一行必须是它</li>
<li><strong>目录名带半角冒号，Android FUSE 拒绝一切写入</strong>：歌单目录 <code>Bar 20:30～</code> 里那个 <code>:</code> 是 FAT 非法字符，Termux 写不进去，<strong>连 root 都写不进去</strong>（root 继承了 app 的挂载命名空间，<code>su -mm</code> 也无效）。本地目录改成全角冒号 <code>Bar 20：30～</code>，视觉几乎无差别</li>
<li><strong>Termux:Boot 靠不住</strong>：MIUI 会把 Termux 打成 <code>stopped=true</code>，Android 不向 stopped 应用投递 <code>BOOT_COMPLETED</code>，Termux:Boot 开机永远不触发。最终把所有守护进程迁到 <strong>Magisk service.d（root 层）</strong>——开机必跑、永远有网、不受任何应用管理影响</li>
</ul>
<p>实测端到端：22:07 歌单加歌 → 22:17 VPS 下好 → 22:18 手机下好，<strong>全程 11 分钟</strong>。</p>
<h2 id="最终结果">最终结果</h2>
<table>
	<thead>
			<tr>
					<th></th>
					<th>设备 1</th>
					<th>设备 2</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>ROM</td>
					<td>OS3.0.9.0 / <code>_b</code> 槽</td>
					<td>OS3.0.10.0 / <code>_a</code> 槽</td>
			</tr>
			<tr>
					<td>Root</td>
					<td>Magisk 30.7</td>
					<td>Magisk 30.6（30.7 在 3.0.10 上卡开机，刻意降级）</td>
			</tr>
			<tr>
					<td>可用内存</td>
					<td>~5.1GB</td>
					<td>~5.1GB</td>
			</tr>
			<tr>
					<td>曲库</td>
					<td>700 首 / 6.2GB</td>
					<td>700 首 / 6.2GB</td>
			</tr>
			<tr>
					<td>远程</td>
					<td>SSH + adb 双隧道（公网）</td>
					<td>SSH + adb 双隧道（公网）</td>
			</tr>
			<tr>
					<td>限充</td>
					<td>80% 停充</td>
					<td>80% 停充</td>
			</tr>
			<tr>
					<td>SIM</td>
					<td>无</td>
					<td>联通 5G SA（数据关闭）</td>
			</tr>
	</tbody>
</table>
<p>几个数字：</p>
<ul>
<li>裁剪后内存可用 <strong>78MB → 5.1GB</strong></li>
<li>音乐自动同步端到端 <strong>11 分钟</strong></li>
<li>5G 实测（设备 2）：下行 24<del>33Mbps，偏低（联通 5G 正常 100</del>300Mbps，套餐限速或信号，待查）</li>
<li>root 层看门狗统一巡检 sshd / frpc / 同步守护 / 电池限充，每 60 秒，谁挂了谁拉起</li>
</ul>
<p>另外写了一个 <code>health-check.sh</code> 例行体检脚本，一条命令出完整报告：身份、服务进程数、网络验证状态、全局代理、电池限充、内存、曲库与 MediaStore 对账、修剪漂移。其中&quot;全局代理&quot;一项是后来加的教训：设备 1 残留过一条 <code>http_proxy 127.0.0.1:8080</code>，<strong>所有应用断网但 root 完全正常</strong>，WiFi 设置界面还显示&quot;已连接&quot;，UI 上根本查不到——这种不对称是排查时最大的误导。</p>
<p>最近还给 Termux 装了 musicfox（网易云音乐终端版），配了 Termux:Widget 桌面快捷方式，不用开 app 直接敲命令听歌。后续打算在它们身上试 NAS（OTG 外接存储）和下载节点。</p>
<h2 id="几点收获">几点收获</h2>
<ol>
<li><strong>先备份一切</strong>。那两份 ~536MB 的 NV 分区备份是这个项目的保命资产——没有它们，解锁那一步根本不敢做。</li>
<li><strong>root 层比应用层可靠</strong>。所有&quot;开机必须跑&quot;的守护进程放应用层（Termux:Boot）全被 MIUI 干掉，迁到 Magisk service.d 之后零失效。</li>
<li><strong>&ldquo;锁文件 + PID&rdquo; 的单实例写法是重灾区</strong>。两个看门狗脚本都因为 PID 复用死锁静默停摆过一天以上（进程死了，PID 被别的进程回收占用，存活检查永远通过）。锁检查必须配合 <code>/proc/&lt;pid&gt;/cmdline</code> 核对身份。</li>
<li><strong>不要在 adb/ssh 里堆嵌套引号</strong>。排查系统状态一律写成脚本文件推上去执行——<code>su -c '...$(cat ...)...'</code> 这种嵌套我把 wakelock 丢失、曲库清零误判了四次，实际都好好的。</li>
<li><strong>自建的乐趣在这</strong>：每个零件都看得见摸得着，坏了修得快，修完还懂了。</li>
</ol>
<hr>
<p><em>本文由 Qwen3.8-27B（OpenRouter）根据两台设备的接手包文档与会话记录整理撰写，我负责审核与修改。</em></p>
]]></content:encoded></item></channel></rss>