Mac Mini 自建服务器

进队之前就很想自己在家里搭个服务器玩玩,进队之后便正式考虑起了这件事情。服务器放在家里肯定要考虑体积、噪音以及功耗的问题,传统的服务器肯定不是合适的选择。再考虑到以后可能要搭建本地 AI 模型、Minecraft Server 甚至作为评测机使用,CPU 以及 GPU 的性能也是非常重要的。综合考量下来,趁着教育优惠入手了 M2 Mac Mini,内存 24G 加满,升级万兆网口,硬盘就内置 256G,之后会走雷电 4 进行拓展,总价 7000 出头还送 AirPods(这个时间节点买 M2 确实比较亏,但是再不赶紧买我就要无聊到烂在家里啦)。

到货之后才发现,感觉比我想象中的要大一些啊(本来以为就比 Apple TV 大一小圈的)。

Mac Mini 开箱

基础设施

拿到 Mac Mini 后,先用一根 HDMI 线连接显示器进行初始的设置。以 macOS Sonoma 为例,打开系统设置,在 General > Sharing 中,将 Remote Management 以及 Remote Login 打开,并点击右侧的 info 图标,设置远程登录密码以及允许远程管理的用户,最后在 Options 中将所有权限都勾选上。建议设置较强的密码,防止被暴力破解。

General > Sharing

Remote Management

Remote Options

由于服务器需要 24 小时开机,因此在设置的 Lock Screen 中,将几个电源选项都设置为 Never。服务器还需要在重启时自动登录,因此在 Users & Groups 中,在 Automatically log in as 选择自动登录的用户。若需要在停电后自动开机,则在 Energy Saver 中开启 Start up automatically after a power failure

Lock Screen

Users & Groups

Energy Saver

接下来便可以将 Mac Mini 关机,断开显示器,放到想要放置的地方了再开机运行了。要从局域网内访问远程桌面,同样以 macOS Sonoma 为例,打开 Screen Sharing.app 并输入 Mac Mini 局域网内的主机名或地址以及登录密码即可,例如我本地的 vnc://yaoxi-stds-mac-mini.local。如果是 Windows 或者 Linux,则可以使用其他 VNC 客户端进行访问。

Screen Sharing.app

接下来的部分便可以根据自己的需求来选择了。

Docker

直接下载 macOS 版本的 Docker Desktop 即可。

我本来想尝试用 OrbStack 代替 Docker Desktop 以优化性能,但毕竟是非官方,还是有一些莫名其妙的问题,比如我在自己的 MacBook Pro 上就无法使用 OrbStack 拉取镜像。

为了能够正常拉取 Docker Hub 的镜像,还需要想办法配置代理。

我在服务器上给 docker 分配了 20GB 的内存,绝对够用。

Docker Desktop

拨号上网

以下步骤建议和 Mac Mini 在同一局域网内进行操作。

macOS 本身支持 PPPoE 拨号上网,因此直接让 Mac Mini 和原本的路由器分别通过网线连接到光猫并分别拨号,就可以省去路由器端口转发的设置了(当然,如果你家的路由器是集成在光猫里的,就不得不设置端口转发了)。不过要先确认拨号上网获取的 IP 地址没有被运营商 NAT。

要设置拨号上网也非常简单,在系统设置的 Network 中,点击右下角的 ... 进行 Add ServiceInterface 选择 PPPoEEthernet 选择用于连接光猫的端口。然后在 PPPoE 设置中填写拨号的账号和密码即可,账号一般是手机号,密码可以打电话问运营商的客服。

既然 Mac Mini 现在放在公司而不是家里(就算放在家里我以后去 THU 上预科也没法物理操作),那么肯定需要让拨号上网能够自动重连的(不然不就废了吗),而且还需要开机自启。所以用一个下面这样的脚本来实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/bash

SERVICE_NAME="PPPoE"

if networksetup -showpppoestatus "$SERVICE_NAME" | grep -q "disconnected"; then
  echo "PPPoE connection is down. Attempting to reconnect..."
  networksetup -connectpppoeservice "$SERVICE_NAME"
else
  echo "PPPoE connection is active."
fi

其中 $SERVICE_NAME 可以使用 networksetup -listpppoeservices 进行查看。

然后还需要开机自启并挂在后台运行,写一个 launchd.plist 作为配置文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>yaoxi-std.pppoe-reconnect</string>

    <key>ProgramArguments</key>
    <array>
      <string>/path/to/connect.sh</string>
    </array>

    <key>StartInterval</key>
    <integer>30</integer>

    <key>RunAtLoad</key>
    <true />

    <key>KeepAlive</key>
    <true />
  </dict>
</plist>

然后安装服务:

1
2
sudo cp launchd.plist /Library/LaunchDaemons/yaoxi-std.pppoe-reconnect.plist
sudo launchctl load /Library/LaunchDaemons/yaoxi-std.pppoe-reconnect.plist

手动断开 PPPoE 进行测试,如果在 30 秒内自动重连则成功。也可以重启服务器再进行测试。

公网访问

中国移动的网络环境依托答辩,家庭宽带全都走 NAT 了,根本没有公网 IP。所以我选择使用中国电信并将 Mac Mini 放在了公司。

公司架子上的 Mac Mini

然而电信家宽封 80 和 443 端口,所以不备案的话无法直接将域名解析到该 IP 进行 HTTP(s) 访问。而我选中的 .dev 域名根据相关规定又不允许备案(不是我不想哈),所以只好使用一些奇技淫巧了。

现在的矛盾在于,如果使用境外的服务器进行转发或者内网穿透,Minecraft 之类的对网络延迟要求较高的服务就会有所影响。

于是我产生了一个天才的想法:使用 2 个子域名分别解析,一个解析到 Cloudflare Tunnel 进行内网穿透,确保在浏览器中输入地址时无需加端口号;另一个直接通过 ddns 解析到服务器拨号产生的动态 IP 上,因为电信并没有封禁 Minecraft Server 默认的 25565 端口。

开搞。秉承将服务全部装进 docker 容器的思想,直接用一个 docker-compose.yml 搞定:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
networks:
  shared-network:
    external: true

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    networks:
      - shared-network
    restart: unless-stopped
    command: tunnel --protocol http2 --no-autoupdate run --token <cloudflare-tunnel-token>
  cloudflare-ddns:
    image: favonia/cloudflare-ddns:latest
    container_name: cloudflare-ddns
    security_opt:
      - no-new-privileges:true
    network_mode: host
    restart: unless-stopped
    read_only: true
    cap_drop: [all]
    environment:
      - CF_API_TOKEN=<cloudflare-api-token>
      - DOMAINS=i.yaoxi-std.dev,mc.yaoxi-std.dev
      - PROXIED=false

其中 cloudflared 使用 shared-network 是为了让服务不暴露在主机端口上,而是直接通过 docker 的内部网络连接到 nginx 再根据域名进行转发到虚拟主机。先使用 docker network create shared-network 创建名为 shared-network 的网络,重启 docker 后无需重新创建。

然后是 Cloudflare Tunnel 的配置。注意下面这个 URL 不能填 localhost:80,而是要填 nginx:80,因为不同容器的内网不是同一个。

Tunnel 配置

注意将 <cloudflare-tunnel-token><cloudflare-api-token> 进行替换。

注意 docker 默认使用系统代理。为了防止 ddns 获取到的 IP 为代理服务器的 IP,需要在 Docker Desktop 中将 1.1.1.1 加入使用代理的 Bypass 名单中。

Docker Desktop 的 Proxy Bypass

记得将 Docker Desktop 也加入开机启动,直接在系统设置中调一下即可。

Nginx

装入 docker 容器中并连接到刚才创建的 shared-network 中即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
networks:
  shared-network:
    external: true

services:
  nginx:
    image: nginx:latest
    container_name: nginx
    networks:
      - shared-network
    restart: unless-stopped
    volumes:
      - ./conf.d:/etc/nginx/conf.d:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

防火墙

既然允许了公网访问,建议还是开启防火墙,在设置中打开 macOS 自带的防火墙即可。

记得将本地搭建的其他服务以及 Docker Desktop 加入防火墙白名单。

防火墙设置

远程桌面

如果你使用 macOS 自带的 Screen Sharing.app 连接远程桌面,会发现没办法更改 Mac Mini 的分辨率,只能用默认的 1080p。

我根据网上的其他博客尝试使用 ScreenResX 来进行调整,但结果是 Mac Mini 插上显示器后可以更改分辨率,断开显示器就无法再更改并会变回 1080p。最后在 Reddit 上找一个叫 BetterDisplay 的软件,其原理是创建一个任意分辨率的虚拟显示器,遂解决。

BetterDisplay 设置

突然发现「台前调度」好像很适合当服务器的监控页面

拯救模式

为了防止因为运营商莫名其妙的原因导致无法直接通过公网 IP 进行 ssh 连接,可以考虑在 Cloudflare Tunnel 中也留一条 ssh 通道。

有的地方运营商会封 5900 端口(我也不知道为啥),但一般不会封 22。这时要想通过 VNC 访问 Mac Mini,则可以通过 ssh 通道进行穿透:

1
ssh -L 5901:localhost:5900 user@hostname

即将远程主机的 5900 端口映射到本地的 5901 端口,这时直接在 VNC 客户端中连接 vnc://localhost:5901 即可。

如果在操作时出现失误导致 ddns 失败,可以尝试在一定范围内更改 IP 地址的最后一位进行连接。如果 PPPoE 挂了就没救了。

Blog

待更。

Minecraft Server

待更。

Online Judge

待更。


最后修改于 2024-08-15