记录使用 Mac Mini M4 配置个人服务器的全过程。
由于正在使用的个人服务器 (Github Education 薅来的一年 Digital Ocean) 即将在 2025年6月17日到期,所以,我决定将其迁移到 Mac Mini 上。
Personal needs
- Services, Development & Automation (Docker 或 OrbStack 运行和管理容器化应用程序)
- ✅ 电子书服务器:Calibre Content Server
- 使用 Docker 运行个人日常所需要的服务(目前包括 RSSHub, miniflux等)
- 将原云服务器上的数据迁移到 Mac mini
- Mercari-bot 迁移 + CI/CD工作流配置
- GitHub Actions Runner,本地执行 CI/CD 作业
- 托管个人博客或其他 Web 应用
- Local LLM
- Monitoring & Maintenance
- ✅ 远程硬件监控服务器的性能指标(CPU、RAM、温度等). See Basic Setup.
- Docker Container 开源监控管理工具选型和配置
- 日志管理平台
- Network
- ✅ Tailscale 实现虚拟组网、远程访问
- Tailscale Funnel 实现部分本地服务发布到公网
- Remote Access
- ✅ 远程访问 Mac mini 服务器,例如通过 Mac 自带的 Screen Sharing 和 Remote Login (SSH). See Basic Setup.
- ✅ 在不连接显示器的情况下运行服务器 (headless 模式),可能需要 HDMI 虚拟插头. See Basic Setup.
01 Basic Setup
- 卸载所有不需要的软件,如 Apple 自带的 GarageBand, Numbers 等
- 在 MacBook, iOS, Mac mini 上安装和配置 Tailscale
- ❓ Tailscale ping 可以 ping 通,但是无法使用 VNC 连接到 mini Screen Sharing
- ✅ 网络代理工具冲突,检查 System Settings -> VPN
- ✅ 参考在🚀TUN Excluded routes里添加TS网段、在🚀Skip Proxy里添加TS网段和域名
- ❓ Tailscale ping 可以 ping 通,但是无法使用 VNC 连接到 mini Screen Sharing
- Remote Access: Screen Sharing, Remote Login (SSH): 在 Mac mini 中的 General -> Sharing 中开启,然后使用 MBP 的 Screen Sharing 和 Terminal (SSH) 连接到 Mac mini.
- Energy 设置 – 目标是让 Mac mini 持续运行不睡眠
- System Settings -> Energy -> turn on all following options
- Wake for network access
- Low Power Mode
- Prevent automatic sleeping when the display is off
- System Settings -> Energy -> turn on all following options
- Mac mini 虚拟屏幕 – 买了一个屏幕欺骗器 (HDMI Dummy Plug),让 Mac mini 能够无头模式 (headless) 运行
- Homebrew 包管理工具安装
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- 然后根据提示运行命令
- CPU / GPU / Network / etc. remote monitor: Stat > Stats Remote Monitor
02 Network
在 MacOS 上同时运行网络工具和 Tailscale
🙋 The Problem
MacOS 不同于 IOS,允许用户同时运行多个网络工具,如 Tailscale 和代理工具 (⭕️X/🚀) 。然而,这种灵活性也带来了路由冲突的问题:多个工具可能试图控制相同的 IP 地址段或网络接口,导致像 Tailscale 这样的虚拟网络流量无法正确路由。即,Tailscale 中显示设备已经 Connected,但是在 所示的情况下,无法 ping 通 Tailscale 网络中的其他设备 IP 地址。

我趁此梳理了 macOS 系统中 CIDR 路由的优先级规则、⭕️X/🚀 等代理工具的 Excluded Routes 和 Skip Proxy 配置的实际行为,以及如何通过路由掩码精度(如 /9
vs /10
)调整优先级,实现在多个工具共存时 Tailscale 的正常工作。
🔎 Identify the Issue
🚀 配置项
如果想确保某些内网地址能够绕过 🚀 的 TUN 接口处理,避免路由被劫持或覆盖,需要将这些地址段加入 TUN Excluded Routes,这样系统会查询系统的 route table (使用命令 netstat -rn
查看),然后按照优先级,判断使用的虚拟网卡。如果 Tailscale 的 route 优先级高于 🚀 的 route,则会使用 utunX 接口进行连接,而不会被 Shadowrocket 截获。
相反,如果希望某些 App 的流量不经过代理服务器但仍由 Shadowrocket 处理(例如用于记录、限速或连接控制),可以将其 IP 或域名添加至 Skip Proxy 列表中。
两者的区别和使用场景详见 ,其他参数及TUN模式的解释见 🚀通用参数。一个详细的案例见后续的Tailscale 访问 Calibre Content Server 。
Skip Proxy | TUN Excluded Routes | |
---|---|---|
用途 | 不通过HTTP代理服务器,而是直接由🚀的TUN接口 (从网络链路层接管所有流量) 转发的域名或IP段。 | 不经过TUN接口转发,而是走系统默认路由 (即查询系统route tables) |
是否通过 TUN 接口? | ✅ 是 | ❌ 否 |
适合场景 | App 不需要代理,但仍希望 🚀 控制连接(如限速、日志) | 希望完全绕过🚀(比如局域网、Tailscale), 查询系统的 route table |
是否经过 🚀? | ✅ 是(通过 TUN 拦截) | 取决于系统的 route table |
🚀 会接触这个流量吗? | ✅ 会处理后直连 | 取决于系统的 route table |
Tailscale 保留网段
Tailscale 分配的默认地址范围为 100.64.0.0/10 地址段,即 100.64.0.0 ~ 100.127.255.255,见Tailscale-assigned IP range,例如:
100.9x.xx.xx
100.1xx.xx.xx
100.7x.xx.xx
所以,将 100.64.0.0/10
加入 🚀的 TUN Excluded Routes 中,按理即可确保 Tailscale 的流量不会被 🚀 劫持。但是,100.64.0.0/10
已经在 🚀 默认的 excluded routes 中,但此时仍然无法 ping 通 Tailscale 网络中的其他设备 IP 地址。
✅ The Solution
参考:macos 下的 stash 和 tailscale 不能一起使用? 里面的回答,
Tailscale 用了一个特殊的虚拟网段 100.64.0.0/10,让它的流量通过它自己的虚拟接口(utunX)来走。 Quantumult X 也会设置一些“排除路由”(excluded routes),但它会把这些流量指定到默认网卡接口,比如 en0(WIFI 接口)。
也就是说,🚀的情况可能也和QX类似,其 excluded routes 的方式是——“往系统路由表添加一条 route,指向系统默认接口(如 en0
),这样,Tailscale 的流量还是被劫持了,不再走 Tailscale 自己的虚拟网卡接口 utunX
:
(base) ➜ route get 100.64.0.1
route to: 100.64.0.1
interface: en0
那么,为什么会“覆盖”?因为在 macOS/iOS 的网络路由系统中,路由优先级是按 更精确(更小的 CIDR,比如 /24) 优先。即 CIDR 越小,匹配范围就越大,粒度就越粗,优先级就越低 ()。如果 CIDR 一样,系统可能就按照顺序优先来匹配先添加的那条路由,这样可能就造成另一条路由失效。
所以,将🚀 excluded routes 的100.64.0.0
的CIDR变小(即匹配范围变大、优先级变低),就能让 Tailscale 的路由优先匹配。尝试修改 100.64.0.0/10
为 100.64.0.0/8
可以立马解决 Tailscale 的路由被覆盖的问题:
(base) ➜ route get 100.64.0.1
route to: 100.64.0.1
interface: utun9
p.s. 修改为 /9
时仍然被覆盖,暂时不知道为什么…
补充:CIDR
CIDR 全称是 Classless Inter-Domain Routing,无类域间路由。其表达式为 IP/位数
,例如 100.64.0.0/10
。是一种 IP 地址+斜杠数字的写法,用来表示某个 IP 网段的范围。其中,/10
是指,前 10
位是网络地址,剩下的位数是主机地址。对于 IPv4 来说,IP 地址是 32 位的,所以 100.64.0.0/10
表示前 10
位是网络地址,剩下的 $32-10=22$ 位是主机地址,也就是,前 10 位固定,后面有 22 位可变。即:
/x
的x越小,固定位数越小,可变位越多,IP 网段的范围就越大,粒度就越粗,在 Mac 路由表中的优先级就越低。
CIDR | 固定位数 | 可变位数 | IP数 | 匹配范围 | 粒度 | 优先级 |
---|---|---|---|---|---|---|
/9 | 9 | 32-9=23 | $2^{23}$ = 8,388,608 | 大 | 粗 | 低 |
/10 | 10 | 32-10=22 | $2^{22}$ = 4,194,304 | 小 | 细 | 高 |
🚥 路由匹配规则:系统在决定一条流量走哪条路由时,会优先选择 CIDR 更“具体”的那一条路由(也就是斜杠后面的数字更大)
题外话,复习一下如何计算100.64.0.0/10 的起始和终止地址 ──
- 将 IP 地址转换为二进制
- 计算起始(主机位全0)和终止(主机位全1)地址
- 将二进制转换为十进制
03 Services, Development & Automation
Calibre Content Server
- 在 Mac mini 上安装 Calibre
- Airdrop MBP 上的 Calibre Database 到 Mac mini
- Calibre 自带的 Content Server – 已经满足需求
在 Mac mini 上安装 Calibre-Web试用看功能能否满足需求 (Highlight 同步)如果不能,卸载 Calibre-Web,仅使用 Content Server
Mac mini 上 Calibre Content Server 无法在其他设备通过 Tailscale 访问
🙋 The Problem
在 Mac mini 上启动 Calibre 的 Content Server,使用默认或自定义端口(如 8081)时:
- ✅ 在本机浏览器使用
localhost:8081
能正常访问 - ❌ 在另一台通过 Tailscale 连接的 MBP 上
- 🌐浏览器访问
http://<tailscale-ip>:8081
无法访问(超时/503) - 💻命令行访问
http://<tailscale-ip>:8081
能正常访问 (curl
orping ip
)
- 🌐浏览器访问
此时 MBP 设置情况如下:
🚀 | 🚀 TUN mode | 🚀 TUN Excluded Routes | 🚀 Skip Proxy | 🚀 Proxy rule list | Tailscale | 系统代理 |
---|---|---|---|---|---|---|
on | off | 100.64.0.0/8 | 🈚️ TS 网段 | 🈚️ TS IP 相关规则 | on | http_proxy, https_proxy, all_proxy 均设置为🚀代理 127.0.0.1:1082 |
🔎 Debug
首先,查看🚀的logs …
浏览器请求 -- 代理接口接管 -- 查找 proxy rule list
[13:00:39.672] proxy stream <51> lookup host => 100.xxx.xxx.xxx:8081
[13:00:39.810] proxy stream <46> disconnect gateway.icloud.com:443 error => Attempt to connect to host timed out reason => none cost => 5012.787104 ms
[13:00:39.811] chain socket <46> remove closed socket => 2403:300:1366::2:5:443
[13:00:39.811] proxy stream <46> remove host => gateway.icloud.com port => 443 total => 19
[13:00:40.044] proxy stream <52> loopback => 1 total => 20
[13:00:40.096] proxy stream <52> async lookup rule url => http://100.xxx.xxx.xxx:8081/ host => 100.xxx.xxx.xxx port => 8081
[13:00:40.097] proxy stream <52> tcp rule => {
result = "IP-CIDR,100.xxx.xxx.xxx/8,DIRECT,no-resolve";
type = DIRECT;
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15";
url = "http://100.xxx.xxx.xxx:8081/";
}
[13:00:40.097] proxy stream <52> lookup host => 100.xxx.xxx.xxx:8081
[13:09:00.557] proxy stream <47> disconnect http://100.xxx.xxx.xxx:8081/ error > Attempt to connect to host timed out reason => none cost => 5010.002017 ms
[13:09:00.557] chain socket <47> remove closed socket => 100.xxx.xxx.xxx:8081
[13:09:00.557] proxy stream <47> remove host => 100.xxx.xxx.xxx port => 8081 total => 13
可以看出,浏览器请求的TS地址被🚀的代理接口接管,然后被proxy rule list命中,直接返回了DIRECT
,即🚀直接发出访问,没有到TS的虚拟网卡接口。
而在Terminal ping/curl 请求 google or ts 网段 时, 🚀 的 logs 里没有相关输出。于是在命令行查看两个地址经过的 route:
route get…
(base) ➜ ~ route get www.google.com
route to: 31.13.112.9
destination: 31.13.112.9
interface: utun4 # 🚀的TUN接口
(base) ➜ ~ ifconfig utun4
utun4: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 4064
inet 198.xx.x.x --> 198.xx.x.x netmask 0xffffff00 # 🚀的TUN接口
(base) ➜ ~ route get 100.xxx.xxx.xxx
route to: mini.xxxxxx.ts.net
destination: 100.64.0.0
mask: 255.192.0.0
interface: utun9 # Tailscale 的虚拟网卡接口
(base) ➜ ~ ifconfig utun9
utun9: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1280
inet 100.1xx.xx.xx --> 100.1xx.xx.xx netmask 0xffffffff # Tailscale 的虚拟网卡接口
inet6 fd7a:115c:a1e0::5001:4b37 prefixlen 48
nd6 options=201<PERFORMNUD,DAD>
说明在命令行中使用 curl or ping 时,直接查询系统的 route table,而没有到 🚀的代理接口查询 proxy rule list。
再次验证猜想…
(base) ➜ ~ proxy
(base) ➜ ~ echo $all_proxy
socks5://127.0.0.1:1082
(base) ➜ ~ unproxy
(base) ➜ ~ echo $all_proxy
(base) ➜ ~ ping www.google.com
PING www.google.com (31.13.94.10): 56 data bytes
64 bytes from 31.13.94.10: icmp_seq=0 ttl=64 time=0.784 ms
64 bytes from 31.13.94.10: icmp_seq=1 ttl=64 time=0.474 ms
^C
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.474/0.629/0.784/0.155 ms
确定了命令行没有走设置的 all_proxy
或者 http_proxy
代理,而是直接查询系统的 route table。
查看系统的 route table…
<!-- ------------------ when 🚀 turned on (TUN mode off) ------------------- -->
(base) ➜ ~ netstat -rn
Routing tables
Internet:
Destination Gateway Flags Netif Expire
default link#38 UCSg utun4 #🚀TUN接口,default —— 代表所有目标地址(0.0.0.0/0),即这条路由为默认路由(default route)
default 10.xxx.xxx.xxx UGScIg en0
...
100.64/10 link#37 UCS utun9 #Tailscale虚拟网卡接口
<!-- ------------------------- when 🚀 turned off -------------------------- -->
(base) ➜ ~ netstat -rn | grep utun4 # 无输出
说明只要🚀打开,不论是否开启 TUN 模式,都会其TUN虚拟网卡接口添加为系统的默认路由。
跳过代理(skip-proxy):跳过代理接口,使用 TUN 接口接管。此选项强制列表中的域名或 IP 的连接范围交由 Shadowrocket TUN 接口 来处理,而不是 Shadowrocket 代理接口。此选项用于提高部分应用程序对于代理环境的兼容性。若开启 TUN 模式 时,将强制使用 TUN 接管所有连接,此处地址列表可以忽略 TUN旁路路由(tun-excluded-routes):Shadowrocket TUN 接口 只能处理 TCP 协议。使用此选项可以绕过指定的 IP 范围,让其他协议通过。TUN (Network TUNnel device) 是 一种虚拟网络接口驱动(虚拟网卡),用于在用户程序和内核网络栈之间传递 IP 层的数据包。其工作层为第三层 (IP层),常用用于 VPN、代理、分流、透明代理等场景。macOS 下接口名一般为 utunX(如 utun2, utun5)。有些软件不遵循系统代理,如终端、iTerm、Infuse。TUN 模式就是为了解决这个问题的,它对于不遵循系统代理的软件,它可以接管其流量并交由代理软件处理。参考🚀的文档…
✅ The Solution
通过上述分析,可以定位问题为,浏览器发出的TS地址请求被🚀代理接口接管,无法到达系统路由表,导致无法访问TS网络。而命令行请求的TS地址直接查询系统路由表,可以正常访问TS网络。
所以,在 🚀的 Skip Proxy 中添加 TS 网段 100.64.0.0/10
即可, optional 可加上 *.ts.net
。
整理了一下浏览器和命令行访问TS网络的流程如下:
flowchart TD %% 命令行 curl / ping 路径 B[⬇️<br/>💻 curl(HTTP协议)<br/> 💻 ping(ICMP协议)] --> B1[查询系统 routes] B1 -->|match ts route| B2[TS utunX网卡] B2 --> B3[🌐✅成功访问TS<br/>💻✅成功访问TS] B1 -->|dont match any route| A14[系统default route] %% 浏览器路径 A[⬇️<br/>🌐 浏览器访问<br/>(HTTP协议)] -->|✅设置了系统代理<br/>http_proxy + 🚀 on| A2[📤 请求发往🚀代理端口<br/>127.0.0.1:1082] A2 --> A3{命中🚀**Skip Proxy**?} A3 -->|否<br/>| A10[🚀代理] A10 --> A19[🌐✅成功访问Google<br/>🌐❌无法访问TS,🚀模拟返回503] A3 -->|是<br/>| A4[跳过🚀代理接口<br/>查询🚀TUN routes] A4 --> A5{命中🚀TUN<br/>**Excluded Routes**?} A5 -->|是| A6[跳过🚀TUN接口] A5 -->|否| A11[🚀TUN接口] A11 --> A19 A6 --> B1 A14 --> A15{🚀 on?} A15 -->|是| A16[🚀utun4网卡,🚀代理] A16 --> A20[💻✅成功访问Google] A15 -->|否| A17[无🚀代理,直连] A17 --> A21[💻❌无法访问Google]
References
- 用Tailscale 构建了我的网络| zhul78
- Home k8s cluster | Omer Atagun
- A Mac mini as a home server | Aaron Parker
- An Idiot’s Guide to Using Mac Mini & MacOS as a Home Server | Reddit
- Mac Mini Home Server | Dakota Chambers
- Mac Mini as a low idle home NAS | Michael Stinkerings
- 请问下大伙们 MacOS M 系列正在使用哪款代理软件 | v2ex
- MacOS 下的 Stash 和 Tailscale 不能一起使用? | v2ex