后 iCloud 时代,我们怎么给手机备份

UPDATE:
定时任务的执行好像还是有问题。嗯,还是乖乖用 iCloud 或者等 iMazing CLI 的价格降到普通人可以接受的水平吧。

最近 iCloud 的几个新闻大家应该都非常熟了,先是迫于美国执法部门的压力放弃了 iCloud 备份的端到端加密,后和执法部门合作对 iCloud 照片进行扫描。诚然,扫描云端数据并不是什么新闻,iCloud 依然是这个星球上最注重隐私的主流云服务,但是这依然是不可以接受的——苹果一直以来的隐私承诺在此前是多么的苍白。

所以,自建一个备份服务器是很重要的。

但是问题来了,我们之所以用 iCloud,其根本原因是因为它太好用了——不需要复杂的设置,小手一点无论在世界的哪里都会自动在晚上睡觉充电时自动备份。这样的系统集成使用体验是很难复刻的。

诸多功能里,最重要的两条:

  • 自动化,没有人想要(或者能想起来去跑)一个手动的备份程序
  • 安全,所有的数据都应该是加密的

这听起来很像 Time Machine,但是很遗憾,我们并没有 Time Machine for iPhone。感谢那个联网还不是计算机标配的年代,否则现在的 Mac 备份可能也要被强制搬到 iCloud 上去。

我的文章的读者家里应该都有 NAS(或至少听说过)。如果能在 NAS 上跑一个自动运行的备份程序,看起来会是个好主意。

没有银弹

没有银弹是一个软件工程行业的谚语,世界上没有能一劳永逸解决所有问题的解决方案。

为了达成我们的目标,首先我们需要一个文件服务器(比如我们的 NAS),其次是一个跑备份程序的计算节点。

怎么部署这个计算节点成了一个更大的问题。iMazing 是一个值得信赖的第三方 iOS 设备管理程序,恰巧满足我们的需求。但可惜的是只有 Windows 和 Mac 版。在容器里面跑是没戏了(如果你不同意这点,我理解,拉到最后),所以唯一的解是在 Windows Server。

和往常一样,我不喜欢直接写结论,而是喜欢写出得出结论的过程。如果你只是需要这个服务能跑起来,可以直接拉到最后。

把大象装进冰箱

把大象装进冰箱只有三步:打开冰箱门,装进大象,关上冰箱门。在这里,这三步就是:起一个 Windows Server,挂载文件服务器,跑起 iMazing。

起一个 Windows Server

没啥可说的,唯一需要注意的是 Windows Server 2016 以上有带完整 GUI 的(所谓的Desktop Experience)和没 GUI 的版本,为了节省资源我用的是没 GUI 的版本,只能用 PowerShell 配置,别的没什么需要注意的。

如果你和我一样选择了没有 GUI 的版本,可以做以下几步让自己舒服一点(如无特殊标注,均为 PowerShell 脚本):

打开 RDP:

Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -name "fDenyTSConnections" -value 0
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"

打开 SSH:

Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType 'Automatic'

然后你应该就可以通过用户名和密码方式访问你的计算节点了(要证书其实也可以,网上能找到很多教程,不表)。

SSH 的主要目的是复制粘贴文字和 scp 方便,由于 Windows 诡异的登录系统,有些命令只能在 RDP 里面跑。如果你在 SSH 里跑命令遇到了奇怪的问题,不妨试试 RDP。

挂载文件服务器

PowerShell 里用来挂载 smb 服务器的命令是 New-SmbMapping,利用一些 Google 技巧,得出最好的方法是先将服务器账号密码保存在钥匙环里,然后就可以安全地被这个命令使用了。

添加秘密到钥匙环:

cmdkey /add:NetBiosOfYourNAS /user:"yourUsername" /pass:"yourPassword"

挂载可持续的(persistent)smb 服务器到 X 盘符:

New-SmbMapping -LocalPath 'X:' -RemotePath '\\NetBiosOfYourNAS\iMazing backups' -SaveCredentials -Persistent $True

这样每次启动都会自动挂载了

跑起 iMazing

为什么要前面要开启 SSH 呢,一个主要的原因就是因为没有 GUI 的版本的 Windows Server 不带 IE,PowerShell 里的 curl-equivalent 在没有 IE 的情况下老报错,我懒得处理这种问题,所以还是把文件直接 scp 进去方便(微软啊微软……)。

涉及到 GUI 程序,所以要用 RDP 连接到虚拟机。直接在 PowerShell 里敲入 C:\Program Files\DigiDNA SARL\iMazing\iMazing.exe 后按照正常的流程去配置,添加新的 iOS 设备和定时备份。第一次连接需要设置/直通 USB 设备。

显然,因为这个版本的 Windows Server 没有完整的 GUI 环境,所以也没有文件选择对话框 😅,所以 iMazing 也自然没法添加新的备份目录。所以还是得装个 vim 手动编辑配置文件(位置是这个 ~\AppData\Roaming\iMazing\Prefs\BackupLocations.plist

当你配置好了 iMazing 的自动备份之后,因为自动备份这个功能是由 iMazing mini 来提供的,所以你需要把它做成一个服务,才能在出现例如意外断电重启的时候自动启动。
使用一些 Google 技巧,我们可以得知在 Windows Server 里自启动的最好的方法是新开一个 Service Account。但是我实在是不想因为这个需求就去折腾 Active Domain(你直接杀了我吧),所以直接用 SYSTEM 看起来是一个更正确的方式。

然后你会发现这根本不管用

每一步都没有问题,为什么?

Windows 有着诡异的用户管理。我没有仔细读过文档,但就我的经验来看,对于 Windows 来说一个完整的 session 必须要用户自己去登录(包括但不限于物理显示器和 RDP)。所以你会发现,在一个 SSH session,有相当多的用户相关功能是不能用的,PowerShell 会报一大堆看似很详细但是无用的错。而你很有可能发现不了这个问题,因为这时候大部分功能是可以用的,你甚至可以用 vscode 的远程 SSH 功能连接 debugger 调试 Windows 下的程序。

所以,如果你发现 PowerShell 报了一大堆奇怪的错,换成 RDP 再试一次大概率能得到解决。

依然是登录的问题。如果你不死心继续尝试,你会发现因为以下原因使用 SYSTEM 用户是不行的:

  • 无头 session(这个不准确的名字是我发明的,本人不是 Windows 专家不知道应该叫啥)无法访问钥匙串
  • SYSTEM 用户有自己的 AppData 目录,因为这个 hacky 需求的本质,我们没有办法完全不使用那个 iMazing.exe 的前端,iMazing 并不开源,你总有需求要配置一些不那么显而易见的东西的

对于这两个问题,我的解决方法是:

  • 明文的 smb 账号密码,直接写在一个 ps1 脚本里
  • 用 Administrator 账号跑这个脚本

为什么要用 Administrator 这个很不安全的账号,我的理由是:

  • 它并不比 SYSTEM 账号更不安全
  • 家庭环境里,一个不暴露任何对外服务的 single-purpose 主机不是一个很大的问题
  • 如果你不死心还是尝试新建一个普通账号,会发现根本不管用

以下是脚本内容,起个名字比如 imazing.ps1

New-SmbMapping -LocalPath 'X:' -RemotePath '\\yourNetBIOS\iMazing backups' -Persistent $True -UserName 'yourUsername' -Password 'yourPassword'
Start-Process -FilePath "C:\Program Files\DigiDNA SARL\iMazing\iMazing mini.exe"

然后把它用定时任务设置成自启动:

$action = New-ScheduledTaskAction -Execute 'PowerShell' -Argument 'C:\Users\Administrator\imazing.ps1'
$settings = New-ScheduledTaskSettingsSet -MultipleInstances Parallel
$trigger = New-ScheduledTaskTrigger -AtStartup
Register-ScheduledTask -TaskName imazing -Action $action -Trigger $trigger -User "$env:USERDOMAIN\$env:USERNAME" -Password 'yourAdminPassword' -Settings $settings

没错,又是明文密码。因为我实在是想不到更安全的方法了,一切都是 tradeoff

然后就完事了

理论上来说,这就应该完事了。我后续倒是有遇到过一个问题,死活无法正常完成备份,日志提示 Cannot run task StandardBackup on device, an other operation is already running on this device!,并且换个目录一切正常。找到的解决方案是删掉一个备份快照,大概就一切正常了。

这是个很 hacky 的解决方案。正常情况下,一个比较好的设计应该是用 iMazing CLI,做成一个 Docker image,这是一个绝好的 Docker for Windows(用 NT 内核的那个,不是 Docker Desktop)的学习和使用机会。同样,异常的处理也会更优雅,这个解决方案完全没有(也没法)处理异常情况。很可惜,iMazing 的 CLI 版本非常贵,几百刀一年的价格就不是给这种需求设计的。Windows 和 PowerShell 诡异的限制也是这个奇怪方案的原因。我怀疑如果 OS X Server 或者 OpenDarwin 还活着都比这个更正常

截至目前这个备份方案看起来一切正常,我打算等再过段时间,确认没有什么暗病之后再停掉 iCloud 备份和订阅。再加上之前已经用 Resilio Sync(同样,不完美的方案,都是 tradeoff)替换掉了 iCloud Drive,剩下还留着的服务就是 iCloud Photo 了。

说老实话,对于普通用户,乖乖接受 iCloud 的审查可能是最好的方法,和同行比大概的确不算烂的。本文所描述备份方案不建议无技术背景的普通群众使用。

以上。