VPN 客户端需要特权帮助才能重新配置网络,而这一事实被过度套用到它周围的一切上。挂载文件共享的脚本不需要 root,因为共享属于已登录用户的会话,也属于该用户的凭据。把挂载移出特权路径,就不必再编写 sudo 规则、root 钩子,也不必改动随应用捆绑的文件。剩下的只是一个常驻打开的小程序,它监视一个配置文件并尝试一次。

为什么这属于用户会话

已挂载的卷是挂载它的那个会话的属性。另一个用户的会话看不到它,而归 root 所有的挂载与 Finder 创建的挂载行为不同。把挂载留在用户会话中就能保留这种语义。

特权辅助工具只用于确实需要它们的那部分工作:创建隧道接口、修改路由、更新解析器。挂载 SMB 共享不属于这些工作。把它当成这类工作,就会被迫在免密码 sudo 与无人值守时运行的 root 脚本之间二选一。这两种做法的风险都超过问题本身,而用户会话方案完全避开了这个选择。

Tunnelblick 自带的连接钩子在这里不能替代,因为它们在桌面会话之外运行,而且与客户端脚本的先后顺序没有定义。从用户小程序里轮询状态是一种更小的机制,假设也更少。

先手工验证共享可用

自动化无法提供并不存在的路由、权限或账户。在写任何脚本之前,先手动连接 VPN 并在 Finder 中打开共享。如果这一步失败,脚本失败时的表现只会更令人困惑。

记录脚本将使用的三个值:Tunnelblick 中列出的配置文件原名、共享 URL,以及挂载后 Finder 显示的卷名。卷名很重要,因为检查现有挂载时用的就是它;而只按名称检查只是一种便利性防护,并不能证明服务器身份。请使用在机器上唯一的名称。然后再确认服务器究竟会不会响应。

smbutil view //files.corp.example
osascript -e 'tell application "Tunnelblick" to get state of first configuration whose name is "Office"'
mount | grep -i smbfs

第一条命令证明服务器会响应,第二条证明配置文件名称可以解析,第三条展示成功挂载的样子。在自动化之前,先把这三项都记下来。

从 Tunnelblick 读取配置文件状态

Tunnelblick 通过 AppleScript 暴露它的配置及其状态,这正是本方案唯一需要的钩子。真正要看的是那一个配置文件所报告的状态字符串,而不是应用是否存在。

轮询有值得说明的代价。比轮询间隔更短的重连可能被完全错过,小程序根本看不到这次切换。对于挂载守卫来说这可以接受,对于任何必须响应每次事件的东西则不可接受。如果需要事件级的保真度,那就不该用这种机制,而不是把定时器调短。选择间隔时请考虑到这个权衡。

property attempted : false

on idle
    set profileName to "Office"
    set shareURL to "smb://files.corp.example/OfficeTeam"
    set volName to "OfficeTeam"
    tell application "Tunnelblick" to set vpnState to state of first configuration whose name is profileName
    if vpnState is not "CONNECTED" then
        set attempted to false
        return 20
    end if
    tell application "Finder" to set mounted to (exists disk volName)
    if (not mounted) and (not attempted) then
        set attempted to true
        try
            mount volume shareURL
        end try
    end if
    return 20
end idle

把它导出为应用并勾选保持打开,然后在登录会话中启动,而不是通过 sudo。

只挂载一次,然后停止尝试

“已尝试”标志正是防止它变成猜测凭据的循环的东西。小程序尝试过一次后,会等到连接状态改变才再次尝试。

这种行为是有意为之。拒绝挂载的共享通常出于重试无法解决的原因,反复尝试会把一次提示变成一连串提示。配置文件离开已连接状态时该标志会重置,因此真正的重连只会产生一次新尝试。退出并重新启动小程序同样会重置它,这就是手动干预的入口。两条路径都不隐瞒失败,都会让失败在 Finder 中保持可见。

脚本不携带任何密码,也应该一直如此。让挂载操作自行询问,让钥匙串做组织所支持的事。把凭据嵌入 URL 会把便利脚本变成必须轮换的机密。

这个辅助方案做不到的事

有三条限制值得写在旁边。它无法在网络故障后弹出繁忙的卷,无法保证状态显示已连接时 DNS 与服务器都已就绪,也不会对每次重连做出反应。

第一条限制在实践中影响最大。服务器消失的共享会留下一个看似已挂载、一访问就阻塞的卷,礼貌的做法是在计划断开之前完成文件操作并弹出它。第二条解释了大多数首次尝试失败的情况:隧道已建立,而解析器还没跟上,所以先等待,再手工挂载一次以确认。第三条是前面说过的轮询权衡,而不是一个需要修补的缺陷。

对于无人值守的机器或受管理的机群,这个方案形态不对。应当使用由管理员掌握的挂载策略,而不是把桌面小程序扩展成特权服务。

相关文章