Mac mini M6 当服务器:ARM 的低功耗与稳定性,加上一整套实测优化
这台 Mac mini 是 9 月 22 日刚发售的 M6 款,我是第二天拿到的。开箱之后它就一直搁在那儿 —— 不是因为不好用,而是因为我手边已经有别的机器干活了,它属于那种「性能过剩到不知道该拿它干嘛」的状态。
后来想清楚了:它其实是家里最适合当服务器的机器。
不是因为便宜(也不便宜),而是因为它同时满足三个通常互相矛盾的条件:功耗低到可以 7×24 不关机、性能够跑一堆容器和设备端模型、噪音小到放在卧室都无所谓。Apple 自己在发布稿里的定位就是「全天候智能体计算工具」和「全天候台式电脑」—— 这基本就是「服务器」的另一种说法。
所以这篇文章分两半:前半段说清楚 ARM 在低功耗和稳定性上到底强在哪(含我自己在这台机器上测出来的数据),后半段是把它当服务器用的完整配置过程,以及过程中撞到的几个坑。
先给结论:
| 维度 | 结论 |
|---|---|
| 空闲功耗 | SoC 实测约 0.3 W(本文实测) |
| 满载功耗 | SoC 稳态约 35 W,整机峰值约 74 W(第三方实测) |
| 闲置噪音 | 官方标称声压级 5 dBA,实测风扇大多数时间停转 |
| 体积重量 | 12.7 × 12.7 × 5 cm,0.67 kg,内置电源 |
| 网口 | 标配 2.5Gb 以太网,可另配 10Gb |
| 能效认证 | ENERGY STAR 能源之星 |
先说这台机器是什么
M6 版 Mac mini 的核心参数(Apple 官网及本机实测一致):
| 项目 | 规格 |
|---|---|
| 芯片 | Apple M6,业界首批用于 Mac 的 2 纳米制程 |
| CPU | 12 核 = 2 个超级核心 + 4 个性能核心 + 6 个能效核心 |
| GPU | 12 核,每核首次内置神经网络加速器 |
| 神经引擎 | 双 16 核 |
| 内存 | 16 GB 统一内存(可选 24 / 32 GB),带宽 153 GB/s |
| 网络 | 100 / 1000 / 2500BASE-T,可选 10Gb |
| 无线 | Apple N1 芯片,Wi-Fi 7 + 蓝牙 6 |
| 接口 | 正面 2× USB-C + 耳机孔;背面 3× 雷雳 4 + HDMI + 以太网 |
| 电气 | 100–240 V AC,最大持续功率 155 W |
| 尺寸 | 高 4.97 cm × 宽 12.70 cm × 深 12.70 cm |
| 重量 | 0.67 kg |
「2 个超级核心」这个分层是新东西。以前是「性能核 + 能效核」两档,现在变成三档:超级核扛最重的单线程突发,性能核处理并行负载,能效核兜底日常琐事。
这个三档设计对服务器场景的意义比跑分大得多。 服务器绝大部分时间跑的是「一堆小任务」—— 日志、心跳、定时任务、容器健康检查。这些活全落在能效核上就够了,性能核和超级核可以待在最低频点睡大觉。这不是理论,从实测数据能直接看出来。
低功耗:实测数据
光看「低功耗」三个字没意义,我把这台机器实际测了一遍。macOS 上读 SoC 功耗要用 powermetrics,它需要管理员权限,采样结果如下(空闲状态,接一台显示器,跑着 WindowServer):
CPU Power: 0 mW
GPU Power: 306 mW
Combined Power (CPU + GPU + ANE): 306 mW
E-Cluster HW active frequency: 1021 MHz
E-Cluster HW active residency: 34.86% (972 MHz: 33% ...)
PS-Cluster HW active residency: 70.04% (... 4788 MHz: 55%)
Current pressure level: Nominal
连续四次采样,整颗 SoC(CPU + GPU + 神经引擎)合计功耗稳定在 294 ~ 324 mW,平均约 0.31 W。
几个细节值得单独说:
① CPU 功耗读数直接是 0 mW。 不是坏了 —— 是这个量级已经低于传感器的分辨精度。相比之下,GPU 那 300 mW 是因为接了显示器、WindowServer 在持续合成画面。如果完全无头运行,这个数还能再降。
② 能效核集群贴在最低频点。 4 个能效核所在集群的工作频率是 1021 MHz,而它们的档位表里最低一档是 972 MHz —— 也就是说 33% 的活跃时间里几乎全跑在最低频。频率表里 1584 MHz 以上的档位活跃度全是 0%。
③ 性能核集群虽然在最高频点,但没有负载。 PS-Cluster 显示 4788 MHz,看起来吓人,但 8 个性能/超级核里有 6 个的活跃度低于 1%。它们只是「通电待命」,不是在干活。
④ 热压等级 Nominal。 这是最理想的一档,说明散热系统毫无压力。
作为对照,第三方评测机构 Notebookcheck 对新款 M6 Mac mini 的实测是:满载时整机功耗峰值约 74 W,随后迅速稳定在 55 W 左右;SoC 部分峰值 45~50 W,稳态约 35 W。同时他们的结论是「空闲状态下的能效比上一代又有提升」。
把这两个数放在一起看:空闲 0.3 W,满载 35 W —— 两个数量级的跨度。 这对服务器来说意味着:平时几乎不发热,偶尔来个大任务也能瞬间顶上去,不需要为了省电而牺牲峰值能力。x86 平台上很难做到这一点,因为即使空闲,整机也很难低于 10 W。
还有两个容易被忽略的硬件级省电设计:
- 网卡支持 Energy-Efficient Ethernet (EEE)。 这是本机
ifconfig的实际输出:media: autoselect (2500Base-T <full-duplex,flow-control,energy-efficient-ethernet>)低负载时物理层会自动降功耗。这不是软件能配出来的,是 PHY 芯片的硬件能力 —— 服务器最讲究的就是这种「持续在线但不需要持续满速」的场景。 - ENERGY STAR 认证 + 闲置声压级 5 dBA。 5 dBA 基本等于「你的耳朵分辨不出来」。实测风扇大部分时间处于停转状态,只有持续满载才转起来。
稳定性:ARM 平台被低估的优势
聊 ARM 服务器,大家第一反应是功耗。但我觉得稳定性才是更值钱的那部分,而且这个优势经常被忽略。
① 统一内存,直接消灭了一整类故障。
x86 机器上有很多奇怪问题都源于内存:颗粒不兼容、XMP 超频不稳、ECC 与主板配合不良、插槽接触氧化导致偶发报错。Mac mini 用的是封装内统一内存 —— 没有插槽、没有兼容性表、没有超频余地,内存带宽是确定的 153 GB/s。
对一台要连续开机几个月的机器来说,「没有任何可动部件和可插拔件」这件事本身就值很多。
② 硬件封闭 = 驱动层没有折腾空间。
Linux 服务器上最耗时间的往往不是应用,而是内核与驱动的兼容性:网卡固件、NVMe 掉盘、电源管理策略冲突。macOS + Apple 自研芯片是垂直整合的,整个栈由一家公司控制。
代价是自由度低(后面会讲到,很多东西你确实改不了),但换来的是它不会在你升级内核之后突然不认硬盘。
③ 厂商自己就是按「全天候」设计的。
这点值得强调。Apple 在新款 Mac mini 的发布稿里反复用「全天候」这个说法 —— 「全天候智能体计算工具」「全天候桌面智能体计算工具」。苹果是明确把这个形态当成常开设备来卖和测的,这和消费级机器「关机拔电」的默认假设完全不同。
④ 热设计余量留得很大。
本机实测热压等级是 Nominal(最理想档)。Notebookcheck 的结论也是「即使在满载状态下,Mac mini 依然保持非常凉爽」,压力测试时风扇噪音 42 dB(A),比上一代还略安静一点。
余量大意味着:你不用担心「夏天开空调前它会不会降频」,也不用担心积灰导致的散热衰减会直接影响业务。
但低功耗不会自动发生:四件事得自己处理
出厂状态的 macOS 是给桌面用户调的,默认策略和「7×24 服务器」是冲突的。这是本次优化的核心。
① 它会睡觉。 初始电源设置里 sleep 1、disksleep 10、autorestart 0 —— 也就是说它不但会睡,而且断电恢复后不会自己开机。
唤醒策略是服务器场景里最容易被漏掉、又最要命的一项。没有 autorestart,一次跳闸之后机器就一直躺在那儿,直到有人手动去按电源键 —— 而对一台无屏机器,这意味着你得搬椅子接线。
② Spotlight 在持续做索引。 三个卷(/、Data、Preboot)索引全开。对桌面用户这是搜索体验,对服务器这是持续的磁盘 IO 和 CPU 唤醒 —— 它会不断把一个本该深度睡眠的芯片叫醒。
③ Siri 与语音服务在后台待命。 服务器用不到语音助手,但相关服务会常驻。
④ 图形界面本身。 程序坞里挂着二十来个图标、桌面合成器一直开着 —— 不影响性能,但影响观感,而且部分是已经从系统里卸载掉的死链接(点了只会弹「找不到应用」)。
实测优化的命令清单:
| 项目 | 命令 | 效果 |
|---|---|---|
| 禁休眠 | pmset -a sleep 0 disksleep 0 |
永不睡眠 |
| 断电自启 | pmset -a autorestart 1 |
来电自动开机 |
| 网络唤醒 | pmset -a womp 1 |
支持 Wake-on-LAN |
| Network Nap | pmset -a powernap 0 |
关掉「睡眠中偷跑任务」 |
| 保活连接 | pmset -a tcpkeepalive 1 |
维持长连接 |
| 关索引 | mdutil -a -i off |
停掉三个卷的索引 |
| 关 Siri | defaults write com.apple.assistant.support "Assistant Enabled" -bool false |
去掉语音服务 |
| 开 SSH | launchctl enable + bootstrap system/com.openssh.sshd |
远程命令行 |
| 精简程序坞 | defaults write com.apple.dock persistent-apps |
20 项 → 5 项 |
验证结果(pmset -g 实际输出):
standby 0 sleep 0
disksleep 0 powernap 0
autorestart 1 womp 1
tcpkeepalive 1 lowpowermode 0
关掉 Spotlight 之后有个副作用要提前知道:mdfind 全盘搜索会失效。 关索引之后我用 mdfind 找一个文件,返回空 —— 不是文件不存在,是索引没了。服务器上这无所谓,但你要是习惯于命令行搜文件,得改用 find。
一个反例:第三方远控会把你的省电设置全部抵消
这部分是本次优化里最有意思的发现。
配置完之后我检查电源断言,发现「禁止休眠」这一项居然是被一个第三方程序持有的:
Listed by owning process:
pid 5442(AweSun): NoIdleSleepAssertion named: "Critical operation"
pid 5442(AweSun): NoDisplaySleepAssertion named: "Critical operation"
pid 5442(AweSun): PreventSystemSleep named: "Critical operation"
pid 5442(AweSun): UserIsActive named: "ForceDisplayOn"
我为了远程看桌面装了向日葵(AweSun),它为了保证远控会话不中断,主动向系统申请了四条电源断言,其中 NoDisplaySleepAssertion 那条的名字直接叫 ForceDisplayOn —— 它会让显示器永远不熄灭。
而 pmset -g 里也直接标出来了:
displaysleep 10 (display sleep prevented by AweSun)
结论很清楚:系统层面的 pmset 设置只是「允许睡眠」,真正的决定权在每个持断言的进程手里。 只要有一个程序不想睡,机器就不会睡。
这是很典型的「省电配置被应用层抵消」。所以如果你也在用第三方远控,别只看 pmset 的输出,要去看 pmset -g assertions —— 那里的 Listed by owning process 才是真相。
顺带一个安全提醒:这类远控工具的守护进程通常以 root 身份 + KeepAlive 自动重启的方式常驻(可以看 /Library/LaunchDaemons/ 下的 plist 确认)。它带来的便利是实打实的,但它同时是一个拥有系统级权限的远程入口 —— 至少确保你知道它开着,并且定期更新。
两个坑,比配置命令本身更值得记
坑一:防火墙的开启顺序
这是唯一一个「做错会把自己锁在外面」的步骤。正确顺序:
① 先把 sshd 和远控程序加入放行列表(--add / --unblockapp)
② 再打开防火墙总开关(--setglobalstate on)
反过来做,防火墙一开,22 端口和远控端口会被一起挡掉 —— 而这台是无屏机器,你只能接键盘鼠标去救。
另外,验证连通性不能用 127.0.0.1。回环流量不经过应用层防火墙的过滤,测了等于没测,必须从另一台机器访问局域网 IP。
坑二:验证手段本身会骗人
这个坑我踩得挺典型。检查 SSH 是否在监听时,我用了:
lsof -iTCP:22 -sTCP:LISTEN # 返回空
于是判断「SSH 没开」。实际上不带 sudo 的 lsof 看不到 root 进程的监听端口 —— 而 sshd 恰恰就是 root 跑的。
正确做法是用 nc 直接探端口,它不需要 root:
nc -z -G 2 127.0.0.1 22 && echo "在监听"
echo "" | nc -w 2 127.0.0.1 22 | head -1 # 顺便抓 banner
换成 nc 之后,实际结果是 SSH 早就正常监听着:
SSH-2.0-OpenSSH_10.3
验证手段不可靠时,会得出完全相反的结论。 这个教训比配置命令本身重要得多。
系统自带应用删不掉,但真的不用删
顺手想清理「地图、音乐、通讯录」这些从来不用的自带应用,结果发现它们和第三方应用不是一类东西:
/Applications |
/System/Applications |
|
|---|---|---|
| 例子 | iMovie、Pages、第三方 app | 地图、音乐、通讯录、照片 |
| 来源 | 单独安装 / 厂商预装 | 操作系统本体 |
| 能否删除 | ✅ | ❌ |
/System/Applications 下全部 65 个应用都带 restricted 标记(ls -ldO 看第 5 列),含义是「root 也不允许删除或改名」,由 SIP 加系统卷密封保证。唯一突破口是关掉 SIP 并破坏密封 —— 而每次系统更新都会重新密封,等于白干。
更要紧的是:删了也省不到空间。
/dev/disk3s1s1 228Gi 13Gi 166Gi 8% /
这 13 GiB 是「整个 macOS 本体 + 这 65 个应用」,作为固定快照存在,删文件不会让可用空间变大。
而且它们对性能的影响实测为 0:launchctl list 里这些应用的相关后台代理一个都没加载,全是按需启动。资源占用取决于「进程」,不是「磁盘上躺着多少文件」。
顺便说一个和「新机」直接相关的点:这些自带应用(iMovie、库乐队、Pages/Numbers/Keynote)出厂就在系统里,我检查时它们的「最后使用时间」全是空的 —— 一台从没被日常使用过的新机,本来就该是这个状态。 这也是我判断「哪些该卸载」最直接的依据。
最后的状态
| 检查项 | 结果 |
|---|---|
| 无用应用 | 卸载 6 个(含 iWork 三件套、iMovie、库乐队),回收约 8.1 GB |
| 孤儿数据 | 清理音色库与媒体索引 490 MB |
| 磁盘 | 已用 22 GB → 14 GB(228 GB 容量) |
| 休眠 | 全关,autorestart 1 |
| Spotlight | 三个卷索引全关 |
| SSH | ✓ 22 端口监听中,SSH-2.0-OpenSSH_10.3 |
| 远程桌面 | 向日葵(第三方);系统自带屏幕共享作为备选方案 |
| 程序坞 | 20 项 → 5 项 |
| 后台自启服务 | 仅保留远控工具自身的守护进程 |
| 热压等级 | Nominal |
值不值
一句话:这台机器当服务器,买的不是性能,是「不用管它」。
如果只看跑分,同价位的 x86 小主机能给到更多核和更多内存。但服务器真正消耗你的从来不是算力,而是三件事:电费、噪音、以及半夜被折腾醒。
M6 Mac mini 在这三件事上的答案分别是:空闲 0.3 W(SoC 实测)、闲置 5 dBA(官方标称)、统一内存 + 封闭硬件带来的「没什么可坏的」。
代价也要说清楚:可维护性差是真实存在的。 内存和硬盘都焊死,买定离手;Wi-Fi 7 被限制在 160 MHz;电源键还在机身底部后角,这个设计确实反人类。
所以它的适用场景很明确 —— 你需要一台安静、省电、能连续跑几个月不出事的常驻机器,并且接受它不能升级。 在这个前提下,它是目前市面上最省心的选择之一。
至于那些「删不掉的系统应用」,等你真的把它当服务器用上一段时间就会发现:你根本不会想起它们的存在。 一台好服务器的最高评价,就是让你忘了它在运行。
附录:两个可以直接运行的脚本
上面那些命令不用一条条手敲。下面这两个脚本是我在自己这台机器上实际跑过的版本 ——
写完这篇文章之后我把它们整理成了公开版:用户名换成 $(whoami)、路径换成 $HOME、
去掉了本机 IP 与主机名。正文里的每一处操作,都对应脚本里的一段。
| 脚本 | 作用 | 下载 |
|---|---|---|
① uninstall-unused-apps.command |
卸载无用应用,全部 mv 进废纸篓(可恢复) |
下载(4 KB) |
② mac-mini-server-setup.command |
服务器化一键配置:10 个步骤 + 结果汇总 + 完整回滚命令 | 下载(13 KB) |
两个脚本的完整内容也贴在下面,嫌下载麻烦可以直接复制到一个新建的文本文件里用。
下载得到的文件没有执行权限、且带隔离属性,双击大概率被 Gatekeeper 拦住。在终端里跑一次就能解决:
cd ~/Downloads
chmod +x mac-mini-server-setup.command uninstall-unused-apps.command
xattr -d com.apple.quarantine mac-mini-server-setup.command uninstall-unused-apps.command
./mac-mini-server-setup.command
两个脚本的行为都是一致的:先打印清单 → 等你输 y → 再要一次登录密码。
看完清单再决定要不要继续,中途输别的字符就取消。
几点必须说清楚:
- 脚本 ② 是幂等的。 每一步都先判断当前状态,已完成的项目会显示「已清理 / 已…」并跳过。 重复运行不会出错,也不会重复备份。所以你可以先跑一次看看,不满意回滚,改完再跑。
- 脚本 ② 的第 2 步是整个流程里唯一不可逆的动作(清空废纸篓)。其余全部是
mv, 事后能从废纸篓「放回原处」。清空失败时它只提示你手动操作,不会用rm -rf动你的文件。 - 清单要按你自己的情况改。
uninstall-unused-apps.command顶部那份应用列表是我机器上的 内容,直接照搬对你不一定合适 —— 先在/Applications里确认准确名字再填进去。 Logic音色库那条要看你装没装 Logic Pro。 它是 GarageBand 与 Logic Pro 共用的, 我机器上没装 Logic Pro 所以直接删;你装了的话,删掉后首次启动会重新下载。- 我没有把「关闭系统自带应用」写进脚本,因为那些应用删不掉、也不该删(原因见上文)。
- 脚本 ② 里的
/Applications/WorkBuddy.app(程序坞第 4 个图标)只是个占位, 改成你自己想常驻的程序即可,不存在会自动跳过。
脚本一:卸载无用应用(全部移入废纸篓,可恢复)
#!/bin/sh
# ============================================================
# Mac 清理:卸载无用应用(全部移入废纸篓,可恢复)
# 用法:Finder 里双击本文件 → 终端中先输 y 回车 → 再输登录密码
# 说明:只做 mv(移动到废纸篓),绝不使用 rm -rf,不碰系统卷
# 适用:macOS 13 及以上(已在 macOS 27 / Mac mini M6 实测)
# ============================================================
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
ME="$(whoami)"
TRASH="$HOME/.Trash"
# ------------------------------------------------------------
# ↓↓↓ 改成你自己要卸载的项目(先在 /Applications 里确认准确名字)↓↓↓
# 下面这份是我自己机器上的清单,作为写法示例保留
# ------------------------------------------------------------
ITEMS="
/Applications/iMovie.app
/Applications/GarageBand.app
/Applications/Pages Creator Studio.app
/Applications/Keynote Creator Studio.app
/Applications/Numbers Creator Studio.app
/Applications/TaierSpeed.app
/Library/Application Support/GarageBand
/Library/Application Support/Logic
"
echo ""
echo "=================================================="
echo " Mac 清理:以下项目将被移入废纸篓(可恢复)"
echo "=================================================="
echo "$ITEMS" | while IFS= read -r p; do
[ -n "$p" ] && echo " $p"
done
echo ""
echo " 说明:/Library/Application Support/Logic 是 GarageBand 与"
echo " Logic Pro 共用的音色库;若你装了 Logic Pro,删掉后"
echo " 首次启动会重新下载,自行判断要不要保留。"
echo ""
echo " 提示:系统自带应用(/System/Applications 下)删不掉,"
echo " 它们带 restricted 标记,且删了也不会释放磁盘空间。"
echo ""
printf " 确认执行?输入 y 回车,其他任意键取消: "
read -r ans
case "$ans" in
y|Y|yes|YES) ;;
*) echo " 已取消,未做任何改动。"; echo ""; exit 0 ;;
esac
echo ""
echo " 正在申请管理员权限(请输入你的 Mac 登录密码,输入时不会显示)..."
sudo -v || { echo " 授权失败,已退出。"; exit 1; }
BEFORE=$(df -k /System/Volumes/Data | tail -1 | awk '{print $4}')
echo ""
echo "=================== 开始移入废纸篓 ==================="
echo "$ITEMS" | while IFS= read -r p; do
[ -z "$p" ] && continue
if [ ! -e "$p" ]; then
echo " 跳过 $p (不存在)"
continue
fi
base=$(basename "$p")
dest="$TRASH/$base"
n=1
while [ -e "$dest" ]; do
dest="$TRASH/$base.$n"
n=$((n+1))
done
if sudo mv "$p" "$dest"; then
echo " 完成 $p"
else
echo " 失败 $p"
fi
done
echo ""
echo "=================== 修正废纸篓内属主 ==================="
echo " (这样你日后可以正常清空废纸篓,无需再输密码)"
echo "$ITEMS" | while IFS= read -r p; do
[ -z "$p" ] && continue
base=$(basename "$p")
for cand in "$TRASH/$base" "$TRASH/$base.1" "$TRASH/$base.2" "$TRASH/$base.3"; do
if [ -e "$cand" ]; then
sudo chown -R "$ME:staff" "$cand" 2>/dev/null && echo " 属主已修正:$(basename "$cand")"
fi
done
done
echo ""
echo "=================== 清理结果 ==================="
echo " /Applications 剩余:"
ls -1 /Applications | sed 's/^/ /'
echo ""
echo " /Library/Application Support 剩余:"
ls -1 "/Library/Application Support" | sed 's/^/ /'
echo ""
AFTER=$(df -k /System/Volumes/Data | tail -1 | awk '{print $4}')
FREED=$(( (AFTER - BEFORE) / 1024 ))
echo " 本次释放空间约:${FREED} MB"
echo ""
df -h /System/Volumes/Data | sed 's/^/ /'
echo ""
echo "=================================================="
echo " 完成。项目都在「废纸篓」里,确认无问题后"
echo " 可在 Finder 中右键废纸篓 →「清倒废纸篓」彻底删除。"
echo " 若想恢复:打开废纸篓,右键项目 →「放回原处」。"
echo "=================================================="
echo ""
脚本二:服务器化一键配置(幂等 · 含回滚命令)
#!/bin/sh
# ============================================================
# Mac mini 服务器化 · 一键配置(幂等,可重复运行)
# 用法:Finder 里双击本文件 → 终端中先输 y 回车 → 再输登录密码(仅一次)
# 适用:macOS 13 及以上(已在 macOS 27 / Mac mini M6 实测)
# ------------------------------------------------------------
# 1) 清理孤儿数据(移入废纸篓) 2) 清空废纸篓
# 3) 开启 SSH 4) 禁止休眠 / 断电恢复
# 5) 关闭 Siri 6) 关闭 Spotlight 索引
# 7) 更新策略 8) 开启屏幕共享(VNC 5900)
# 9) 开启防火墙并放行远程服务 10) 精简程序坞
# 11) 结果汇总与全部回滚命令
# ------------------------------------------------------------
# 幂等说明:每一步都先判断当前状态,已完成的项目会显示「已…」并跳过,
# 重复运行不会出错,也不会重复备份。
# 安全说明:所有删除动作都是 mv(移到废纸篓);唯一的真删除是第 2 步
# 清空废纸篓,且失败时只提示你手动操作,不会用 rm -rf 动你的文件。
# ============================================================
export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
U="$(whoami)"
HOME_DIR="$HOME"
TRASH="$HOME_DIR/.Trash"
BACKUP_DIR="$HOME_DIR" # 程序坞备份落在主目录,可自行改
STAMP=$(date +%Y%m%d-%H%M%S)
DOCK_BACKUP="$BACKUP_DIR/dock-backup-$STAMP.plist"
FW=/usr/libexec/ApplicationFirewall/socketfilterfw
VNC_BIN="/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/MacOS/AppleVNCServer"
# 程序坞第 4 个图标:换成你自己想常驻的程序,不存在会自动跳过
DOCK_EXTRA="/Applications/WorkBuddy.app"
echo ""
echo "################################################################"
echo "# Mac mini 服务器化配置"
echo "################################################################"
echo ""
echo " 【1】孤儿数据 → 废纸篓(已清理过会自动跳过)"
echo " /Library/Audio/Apple Loops、Impulse Responses"
echo " /Library/Application Support/iLifeMediaBrowser"
echo " 【2】清空废纸篓(不可逆!先确认没有要留的东西)"
echo " 【3】开启 SSH(远程登录)"
echo " 【4】禁止休眠 + 断电后自动开机"
echo " 【5】关闭 Siri"
echo " 【6】关闭 Spotlight 索引(副作用:mdfind 全盘搜索会失效)"
echo " 【7】更新策略:留安全响应,禁自动装系统大版本"
echo " 【8】开启屏幕共享(VNC 5900,局域网可远程控制图形界面)"
echo " 【9】开启防火墙,并放行 sshd / 屏幕共享"
echo " 【10】精简程序坞(自动备份,可还原)"
echo ""
echo " 注意:第 9 步开了防火墙之后,如果你的 SSH 端口不是默认 22,"
echo " 或后面还要用别的远程端口,请先改成对应端口再加放行规则。"
echo ""
printf " 确认执行?输入 y 回车:"
read -r ans
case "$ans" in
y|Y|yes|YES) ;;
*) echo " 已取消,未做任何改动。"; echo ""; exit 0 ;;
esac
echo ""
echo " 正在申请管理员权限(请输入 Mac 登录密码,输入时不显示)..."
sudo -v || { echo " 授权失败,已退出。"; exit 1; }
BEFORE=$(df -k /System/Volumes/Data | tail -1 | awk '{print $4}')
# ===============================================================
echo ""
echo "############### 1. 清理孤儿数据 ###############"
for p in "/Library/Audio/Apple Loops" \
"/Library/Audio/Impulse Responses" \
"/Library/Application Support/iLifeMediaBrowser"; do
if [ ! -e "$p" ]; then echo " 已清理 $p"; continue; fi
base=$(basename "$p"); dest="$TRASH/$base"; n=1
while [ -e "$dest" ]; do dest="$TRASH/$base.$n"; n=$((n+1)); done
if sudo mv "$p" "$dest"; then
echo " 完成 $p"; sudo chown -R "$U:staff" "$dest" 2>/dev/null
else
echo " 失败 $p"
fi
done
# ===============================================================
echo ""
echo "############### 2. 清空废纸篓 ###############"
if osascript -e 'tell application "Finder" to empty the trash' >/dev/null 2>&1; then
echo " 完成 废纸篓已清空"
else
echo " Finder 方式失败。请手动:右键「废纸篓」→「清倒废纸篓」。"
echo " 本脚本不会用 rm -rf 替你删除个人文件。"
fi
# ===============================================================
echo ""
echo "############### 3. 开启 SSH ###############"
sudo launchctl enable system/com.openssh.sshd 2>/dev/null
sudo launchctl bootstrap system /System/Library/LaunchDaemons/ssh.plist 2>/dev/null
sudo systemsetup -setremotelogin on >/dev/null 2>&1
# 验证端口必须用 nc:不带 sudo 的 lsof 看不到 root 进程的监听端口
if nc -z -G 2 127.0.0.1 22 2>/dev/null; then
echo " 验证 ✓ 端口 22 在监听"
echo " $(echo '' | nc -w 2 127.0.0.1 22 2>/dev/null | head -1)"
else
echo " 验证 ✗ 端口 22 未监听 → 请到「系统设置 → 通用 → 共享 → 远程登录」手动打开"
fi
# ===============================================================
echo ""
echo "############### 4. 禁止休眠 / 断电恢复 ###############"
sudo pmset -a sleep 0 disksleep 0 powernap 0 womp 1 autorestart 1 tcpkeepalive 1
pmset -g | grep -E "(sleep|disksleep|autorestart|womp|powernap|tcpkeepalive)" | sed 's/^/ /'
echo " 提示 若这里显示 sleep prevented by <某程序>,说明有程序持有电源断言,"
echo " 去 pmset -g assertions 看「Listed by owning process」才是真相。"
# ===============================================================
echo ""
echo "############### 5. 关闭 Siri ###############"
sudo -u "$U" defaults write com.apple.assistant.support "Assistant Enabled" -bool false 2>/dev/null
sudo -u "$U" defaults write com.apple.Siri "Siri Data Sharing Opt-In Status" -int 2 2>/dev/null
sudo -u "$U" defaults write com.apple.Siri "VoiceTriggerUserEnabled" -bool false 2>/dev/null
sudo -u "$U" killall Siri 2>/dev/null
echo " 完成 已写入关闭键值(可在「系统设置 → Apple 智能与 Siri」确认显示为关闭)"
# ===============================================================
echo ""
echo "############### 6. 关闭 Spotlight 索引 ###############"
sudo mdutil -a -i off 2>&1 | sed 's/^/ /'
echo " 副作用 关索引后 mdfind 会返回空(不是文件不存在),改用 find"
# ===============================================================
echo ""
echo "############### 7. 自动更新策略 ###############"
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled -bool true
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload -bool false
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -bool false
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate CriticalUpdateInstall -bool true
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate ConfigDataInstall -bool true
sudo defaults write /Library/Preferences/com.apple.commerce AutoUpdate -bool false
echo " 完成 保留安全响应(CRS/Critical),禁止自动安装系统大版本升级"
# ===============================================================
echo ""
echo "############### 8. 开启屏幕共享(远程桌面)###############"
sudo launchctl enable system/com.apple.screensharing 2>/dev/null
sudo launchctl bootstrap system /System/Library/LaunchDaemons/com.apple.screensharing.plist 2>/dev/null
if ! nc -z -G 2 127.0.0.1 5900 2>/dev/null; then
echo " 首次未起效,尝试 kickstart 唤醒..."
sudo launchctl kickstart -k system/com.apple.screensharing 2>/dev/null
fi
# 允许本机所有账户连接(仍必须输入该账户的登录密码)
sudo defaults write /Library/Preferences/com.apple.RemoteManagement ScreenSharingReqPerm -bool false
sudo defaults write /Library/Preferences/com.apple.RemoteManagement ARD_AllLocalUsers -bool true
IP=$(ipconfig getifaddr en0 2>/dev/null)
if nc -z -G 2 127.0.0.1 5900 2>/dev/null; then
echo " 验证 ✓ 端口 5900 在监听,屏幕共享已可用"
[ -n "$IP" ] && echo " 连接方式:访达 → 前往 → 连接服务器 → vnc://$IP"
[ -n "$IP" ] && echo " 或另一台 Mac 打开「屏幕共享」App 输入 $IP"
else
echo " 验证 ✗ 端口 5900 未监听 → 请到「系统设置 → 通用 → 共享 → 屏幕共享」手动打开"
fi
echo " 安全提醒:当前允许本机所有账户连接(仍需知道该账户密码)。"
echo " 想收紧就改成只允许指定用户;这类远程入口请务必保持更新。"
# ===============================================================
echo ""
echo "############### 9. 开启防火墙并放行远程服务 ###############"
echo " 先加放行规则,再开防火墙(顺序反了会把自己锁在外面)..."
$FW --add /usr/sbin/sshd >/dev/null 2>&1
$FW --unblockapp /usr/sbin/sshd >/dev/null 2>&1
echo " 放行 /usr/sbin/sshd"
if [ -e "$VNC_BIN" ]; then
$FW --add "$VNC_BIN" >/dev/null 2>&1
$FW --unblockapp "$VNC_BIN" >/dev/null 2>&1
echo " 放行 AppleVNCServer(屏幕共享)"
fi
sudo $FW --setglobalstate on >/dev/null 2>&1
echo ""
$FW --getglobalstate 2>&1 | sed 's/^/ /'
echo " 当前放行列表:"
$FW --listapps 2>&1 | grep -B1 "Allow incoming connections" | grep -E "^ *[0-9]+ :" | sed 's/^/ /'
echo ""
echo " 连通性验证(走局域网 IP,回环流量不经过防火墙,测了等于没测):"
if [ -n "$IP" ]; then
for pr in 22 5900; do
if nc -z -G 3 "$IP" $pr 2>/dev/null; then echo " ✓ $IP:$pr 可连通"; else echo " ✗ $IP:$pr 不通"; fi
done
else
echo " 未取到以太网 IP(en0),跳过"
fi
# ===============================================================
echo ""
echo "############### 10. 精简程序坞 ###############"
if defaults export com.apple.dock "$DOCK_BACKUP" 2>/dev/null; then
echo " 备份 $DOCK_BACKUP"
defaults write com.apple.dock persistent-apps -array
add() {
defaults write com.apple.dock persistent-apps -array-add \
"<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>file://$1/</string><key>_CFURLStringType</key><integer>15</integer></dict></dict><key>tile-type</key><string>file-tile</string></dict>" \
&& echo " 添加 $(basename "$1")"
}
# 访达不在 persistent-apps 里(系统硬编码第一位),重建时无需包含
add "/System/Applications/Utilities/Terminal.app"
add "/System/Applications/Utilities/Activity Monitor.app"
add "/System/Applications/System Settings.app"
if [ -e "$DOCK_EXTRA" ]; then
add "$DOCK_EXTRA"
else
echo " 跳过 $DOCK_EXTRA (不存在,改脚本顶部的 DOCK_EXTRA 即可)"
fi
defaults write com.apple.dock show-recents -bool false
killall Dock 2>/dev/null && echo " 完成 Dock 已重启生效"
else
echo " 备份失败,已跳过程序坞精简"
fi
# ===============================================================
echo ""
echo "############### 汇总 ###############"
echo ""
echo "--- /Applications ---"; ls -1 /Applications | sed 's/^/ /'
echo ""
echo "--- 对外服务端口 ---"
for pr in 22 5900; do
nc -z -G 2 127.0.0.1 $pr 2>/dev/null && echo " ✓ $pr 监听中" || echo " ✗ $pr 未监听"
done
echo ""
echo "--- 程序坞 ---"
defaults read com.apple.dock persistent-apps 2>/dev/null | grep -A1 '_CFURLString"' \
| grep -o 'file://[^"]*' | sed 's|file://||; s|/$||' | sed 's/^/ /'
echo ""
AFTER=$(df -k /System/Volumes/Data | tail -1 | awk '{print $4}')
echo " 本次释放约 $(( (AFTER - BEFORE) / 1024 )) MB"
df -h /System/Volumes/Data | sed 's/^/ /'
echo ""
echo "################################################################"
echo "# 回滚命令(需要时逐条执行)"
echo "################################################################"
echo " 关闭屏幕共享 : sudo launchctl bootout system/com.apple.screensharing; sudo launchctl disable system/com.apple.screensharing"
echo " 关闭防火墙 : sudo $FW --setglobalstate off"
echo " 取消放行 : sudo $FW --remove /usr/sbin/sshd"
echo " 关闭 SSH : sudo systemsetup -setremotelogin off"
echo " 恢复索引 : sudo mdutil -a -i on"
echo " 恢复休眠 : sudo pmset -a sleep 1 disksleep 10 powernap 1"
echo " 恢复自动升级 : sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates -bool true"
echo " 还原程序坞 : defaults import com.apple.dock \"$DOCK_BACKUP\" && killall Dock"
echo ""
echo " SSH 连接:ssh $U@${IP:-<本机IP>}"
echo " 屏幕共享:vnc://${IP:-<本机IP>}"
echo " 主机名可用:scutil --get LocalHostName 查看(形如 xxx.local)"
echo "################################################################"
echo ""