越过 Mac,走向 Windows

服役已久的 2015 年款 13 寸 MacBook Pro 当前运行起来实在有点吃力了,因此换电脑的计划逐渐提上日程。考虑到毒瘤的 Apple T2 这一因素,以及现在的 Mac 价格越来越高,我开始考虑其它品牌的笔记本。
大概从去年开始,我就通过 Windows To Go 体验了一下现在的 Windows 10。不得不说和 2015 年的 Windows 10 相比有了巨大的提升,更加适合开发使用了。经过数个月的考察,时机已经成熟。
恰逢 Surface Book 3 发布,于是通过一些渠道,我成功以大约官网一半的价格得到了一台 15 寸顶配 16GB 内存与 1TB 硬盘的 Surface Book 2 作为主力机使用。
终于,我找回了 2015 年看 Surface Book 发布会时的尖叫。
Surface 截图
(从这张截图的时间和发布的时间就可以知道我咕咕咕了多久

找回熟悉的体验

首先,Mac 上诸多人性化的设计,是我们在 Windows 上首先需要找回的。

空格键预览

我们知道 macOS 有一个非常好的设计就是快速预览,俗称“一指禅”。幸好,在 Windows 上我们有类似的替代品。
它就是 QuickLook
QuickLook 展示图片
这款应用十分强大,无论是图片、视频还是代码,它都能很好的预览。安装插件后,Office 文稿等等也不在话下。

终端

对于 Mac 的终端,在 Windows 上现在我们有 Windows Terminal,这也是题图中出现的 Terminal。
对于这个终端,我们从中启动 WSL 的 shell 后,其工作目录通常是 Windows 的系统目录或者用户目录,如果想要让它使用 Linux 中用户的主目录,可以在相应的 profile 中设置如下:

"startingDirectory" : "//wsl$/【WSL 发行版名】/home/【用户名】"

该终端定制性很好,可以参阅官方文档做进一步的自定义。

浏览器

我在 Mac 上的主力浏览器是 Safari,主要看中了它的高效能,因此我可以打开一大堆标签页。
而在 Windows 上,Edge(旧版)可以很好的贴合我的需求。
关于 Edge 的书签问题,详见Edge 书签与 IE 同步
当然,有人会提出 Edge 的代理支持不好,这个会在下面提到。

备份

Mac 以其强大的时间机器备份深受用户喜爱,遗憾的是至今在 Windows 上还尚未有能完全与时间机器匹敌的解决方案。不过,我们仍然可以使用“备份与还原 (Windows 7)” 来进行备份,只需前往控制面板寻找即可。

这里我们只需要备份系统映像即可。

Windows 系统调优

Edge 书签与 IE 同步

你可以在组策略的计算机配置->管理模板->Windows 组件->Microsoft Edge 下找到“使收藏夹在 Internet Explorer 和 Microsoft Edge 之间保持同步”,并启用它。
但你也可以使用注册表:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\SOFTWARE\Policies\Microsoft\MicrosoftEdge\Main]
"SyncFavoritesBetweenIEAndMicrosoftEdge"=dword:00000001


使用 UTC 时间

偶尔我们可能会需要在 Windows 和其它操作系统之间切换。由于 Windows 默认不使用 UTC 时间作为 BIOS 时间,而其它系统通常使用,因此多系统切换会导致时间混乱。我们可以使 Windows 使用 UTC 时间。通过注册表:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation]
"RealTimeIsUniversal"=dword:00000001

屏蔽 Edge Chromium 更新

微软基于 Chromium 推出了全新的 Edge。尽管它的表现也不错,但是旧版 Edge 中有许多令人喜爱的功能是新版 Edge 所没有的。因此我们可能不希望新版 Edge 取代它,而是选择安装 Beta 版 Edge Chromium 配合使用。
好在微软提供了工具包来屏蔽:MSEdge Blocker Toolkit
如果你不幸已经更新到 Edge Chromium,可以使用以下方法(摘编自Carton的回答 – 知乎

  1. 用管理员身份打开命令提示符(Windows键+Q,输入cmd,找到命令提示符,右键-以管理员身份运行)
  2. 输入 cd "C:\Program Files (x86)\Microsoft\Edge\Application\83.0.478.61\Installer" 然后按回车,其中“83.0.478.61”是版本号,要与自己的Edge版本对应,路径视自己的情况更改,一般相同。
  3. 输入 setup.exe --uninstall --system-level --verbose-logging --force-uninstall
  4. 亲爱的蓝色e回来啦!!!

WSL 相关

WSL,全称适用于 Linux 的 Windows 子系统(Windows Subsystem for Linux),可以说是 Windows 近年来吸引 macOS 开发者用户最大的杀手锏了。其中 WSL 分 WSL1 和 WSL2。

WSL1 是微软对 Linux 进行了 clean-room,然后实现出了一个兼容的 Linux 内核。但它并非真正的 Linux 内核,也有许多功能没有实现,例如它没有完整的网络栈,自然就没有了强大的 netfilter 子系统,以及 PID namespace 等。WSL2 则是将真正的 Linux 内核放入 Hyper-V 运行,并进行了一些修改使其与 Windows 配合。

我不否认 WSL2 具有一定的优越性,但 WSL1 有着更加统一的体验与更少的割裂感,因此目前我坚持使用 WSL1,仅使用 WSL2 作为 Docker 后端。本文该部分也以 WSL1 为主。

安装与维护 WSL 发行版

首先,在控制面板->程序与功能->打开或关闭 Windows 功能中,将“适用于 Linux 的 Windows 子系统”打开。

你当然可以在商店中安装 WSL,但是这样 WSL 的数据文件在 UWP 的数据文件夹中,这并不方便维护。因此我建议通过 tarball 安装。
Ubuntu 是提供了专供 wsl 的 rootfs 的,例如 Ubuntu 20.04 Focal Fossa 的 rootfs for WSL

下载后,我们可以通过 wsl.exe 来导入发行版,例如:

wsl.exe --import Ubuntu C:\WSL\Ubuntu C:\Users\Victor\Downloads\focal-server-cloudimg-amd64-wsl.rootfs.tar.gz

这一步操作是将 focal-server-cloudimg-amd64-wsl.rootfs.tar.gz 这个 tarball 作为发行版 Ubuntu 安装至 C:\WSL\Ubuntu

wsl.exe 支持方便地导入和导出 WSL 发行版,因此,如果你想要备份你的发行版,一个很好的例子就是:

wsl.exe --export Ubuntu Ubuntu.tar

注意这里 wsl.exe 是可以将 tar 内容输出到 stdout 的,因此你可以方便地利用管道调用 gzip 等等。

当我们导入一个 WSL 发行版时,我们会发现默认用户为 root。而 wsl.exe 本身是没有提供修改默认登录用户的功能的。好在我翻阅 WSL 在 GitHub 上的 Issue 列表时发现了一条 comment 提供了解决方法如下:

Get-ItemProperty Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss\*\ DistributionName | Where-Object -Property DistributionName -eq 【发行版名】 | Set-ItemProperty -Name DefaultUid -Value 【待指定用户 UID】

服务开机自启

对于 Linux 来说,服务的自启动是一个重要的功能。很不幸的是 WSL 的 init 并非 SysVinit 或者 systemd,而是微软开发的私有 init。这个 init 没有启动服务的功能。

对于 WSL2,或许可以使用 PID namespace 的一些骚操作实现启动 systemd,但 WSL1 不能。显然我们自行实现 systemd 的服务启动职能是比较困难的,且 cgroups 等等功能的缺失也让我们难以实现部分功能。因此我退而求其次,由于 WSL 不存在引导,我们只要部分实现 inittab 和服务启动即可。

以下以 Ubuntu 20.04 为例。

首先,我们通过 update-rc.d,生成 rc.d 脚本,以供使用。
由于 WSL 不存在引导,因此我们需要借用 Windows 的服务来实现 WSL 的启动。
创建 /etc/rc.windows,填入以下内容:

#!/bin/sh
INIT_LEVEL=$(grep -ve '^#' /etc/inittab 2> /dev/null | grep 'initdefault' | head -n 1 | cut -d ':' -f 2)
if [ -n $INIT_LEVEL ]; then
    printf '%d' $INIT_LEVEL > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        for service in /etc/rcS.d/S*; do
            $service start
        done
        for service in /etc/rc${INIT_LEVEL}.d/S*; do
            $service start
        done
    fi
fi
/etc/rc.local
/bin/login

这样该脚本首先在 /etc/inittab 找寻 initdefault 配置来确定用户需要的目标 init 等级,我们可以在 /etc/inittab 中填入:

id:3:initdefault:

这样 init 等级就设为 3。

这时候我们需要配置 Windows 服务,你当然可以使用 Windows 自带的 sc 或者注册表。但是我个人更喜欢 nssm。但无论你使用什么,配置方法都是一样的。
服务启动的二进制程序应当设置为 %SystemRoot%\system32\wsl.exe,启动位置随意,参数部分才是真正重要的:

-d Ubuntu --user root /bin/sh /etc/rc.windows

其中 Ubuntu 为 WSL 发行版名称。

注意,WSL 发行版的所有权是属于单个用户的,因此这个服务需要以你的帐户的身份启动,你需要设置启动帐户为你的帐户。但是这里有一个坑点,如果你使用 Windows Hello 或者 PIN 码等方式登录操作系统,你需要注销然后使用密码登入的环境下进行服务启动帐户的设置,否则会鉴权失败。

至此,WSL 的服务配置已经完成。

Awesome WSL!

在 WSL 中有许多奇技淫巧可以使得 WSL 与 Windows 更好地结合。

比如,我们可以安装一些 X Window 服务器例如 VcXsrvX410 等来使用 Linux 程序的图形界面。只需安装这些程序,并在 WSL 中设置环境变量 DISPLAY=0:0

我们还可以安装 Node.js 程序 wsl-open 来模拟实现 macOS 中 open 或 Linux 桌面中的 xdg-open 的效果。

macOS 的 pbcopypbpaste 这两个可以直接对剪贴板进行读写的命令也是深受用户喜爱的。在 WSL 中,我们可以通过直接调用 %SystemRoot%\system32\clip.exe 来实现 pbcopy。对于 pbpaste,我们可以使用一个第三方小程序 paste 来实现。
但是对于这个 paste,它输出时换行符是 Windows 式的 CRLF,而不是 Unix-like 操作系统常用的 LF,如果你把 paste 放在 C:\bin 下我们可以这样写 pbpaste

#!/bin/sh
"/mnt/c/bin/paste.exe" $@ | sed 's/\r$//g'

有时候我们可能希望使用 WSL 中的程序播放音频,这时候我们可以安装 PulseAudio for Windows 并设置环境变量 PULSE_SERVER=tcp:localhost

使用 WSL 中的 git 时,我们会发现每次都需要输入凭据,这是因为 WSL 中没有钥匙串等凭据管理器,我们可以安装 Git for Windows,然后:

git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/libexec/git-core/git-credential-manager.exe"

另外,我们可能希望 WSL 中访问的 Windows 卷能够正确设置权限,只需在 /etc/wsl.conf 中配置:

[automount]
enabled = true
options = "metadata,umask=22,fmask=11"
mountFsTab = false

根据官方文档,Linux 权限在 DrvFS 下是以 NTFS metadata 的形式存储。因此对于 FAT 系列文件系统可能不能正常存储权限。

我们在 WSL 中运行 Windows 程序的时候通常需要添加 .exe 后缀,这不够好。我们可以通过对 shell 的未找到命令的处理函数进行修改来实现不加 .exe 后缀执行。下面以 zsh 为例,bash 用户可自行修改:

function command_not_found_handler {
    WINCMD="cmd.exe"
    PWSH="powershell.exe"
    prog="$1"
    shift
    function _validate_cmdsuffix {
        type ${prog}.$1 > /dev/null 2>&1
    }
    function _validate_cmdintc {
        ! ${WINCMD} /c help "$1" > /dev/null 2>&1
    }
    function _throw_command_not_found {
        echo "zsh: command not found: $1" > /dev/stderr
        return 127
    }
    if [ $(grep -e "\.(exe|com|bat|cmd)$" <<< "$prog") ]; then
        _throw_command_not_found $prog
    else

        if
            _validate_cmdsuffix exe || _validate_cmdsuffix com || _validate_cmdsuffix bat || _validate_cmdsuffix cmd || \
            _validate_cmdintc ${prog}
        then
            ${PWSH} ${prog} $@
        else
            _throw_command_not_found $prog
        fi
    fi
}

我们还可以使用 binfmt 来实现 vbs 等脚本的直接执行,不过限于篇幅在此按下不表,今后可能会开一个 blog 专门谈谈。

网络

我们通过一个 Hyper-V 的 Linux 虚拟机来辅助 Windows。主要目的是绕开 UWP 环回限制使用代理,以及更好地使用 n2n 等服务。

基础配置

首先,我们在控制面板->程序与功能->打开或关闭 Windows 功能中启用 Hyper-V。然后在 Hyper-V 管理器中新建一个交换机,我们称之为 Router Network
然后新建一个 Linux 虚拟机,将其加入主网桥和 Router Network
这里,我们设置 Windows 在 Router Network 中的 IP 地址为 100.98.22.33,虚拟机的 IP 地址为 100.98.22.1

代理

假设我们在 Windows 上启动的代理监听于 127.0.0.1:1080
首先,我们需要配置 Windows 的 netsh 端口转发,将代理端口转给虚拟机可以连接的端口:

netsh interface portproxy add v4tov4 listenport=1079 connectaddress=127.0.0.1 connectport=1080 listenaddress=100.98.22.33

这样我们把 100.98.22.33:1079 转发到 127.0.0.1:1080
然后我们配置 Windows 防火墙,在高级防火墙配置中建立一条自定义入站规则,协议类型选择 TCP,本地端口使用 1079,作用域中选择远程 IP 地址为下列 IP 地址,并把虚拟机的 IP 地址添加,操作为允许连接。

我们可以在虚拟机中安装你喜欢的 HTTP 代理转换软件,这里以 polipo 为例,配置好 polipo 之后(假设监听端口 8123),就可以在 Windows 中设置代理为 http://100.98.22.1:8123 来实现代理。

n2n

这里我们用 n2n 举例,因为它在 Windows 上运行会比较令人烦躁。
首先我们安装 n2n,该过程按下不表,之后可能会写一篇 blog 谈谈,这里假设 n2n 的 IP 地址为 10.11.20.2/24
我们首先需要在 Windows 上配置永久路由表:

route.exe -p add 10.11.20.0 mask 255.255.255.0 100.98.22.1

然后,我们在虚拟机中配置 NAT 与 DNAT,即可几乎将虚拟机隐形,所有请求都好像是直接发向 Windows 的:

echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -A FORWARD -j ACCEPT
iptables -t nat -A POSTROUTING -s 100.98.22.33 -j MASQUERADE
iptables -t nat -A PREROUTING -d 10.11.20.2 -j DNAT --to-destination 100.98.22.33

2 Replies to “越过 Mac,走向 Windows”

    1. TAP 模式确实不错,不过当时我不清楚为什么在我的电脑上 SSTap 和 Clash 的 TUN/TAP 模式性能都不太理想,因此就用了 Hyper-V 虚拟机。
      其实这里 Hyper-V 虚拟机主要是方便跑 n2n,n2n 在 Windows 上原生跑实在太人间疾苦。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注