越过 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 发布会时的尖叫。 找回熟悉的体验

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

空格键预览

我们知道 macOS 有一个非常好的设计就是快速预览,俗称“一指禅”。幸好,在 Windows 上我们有类似的替代品。 它就是 QuickLook终端

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

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

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

浏览器

我在 Mac 上的主力浏览器是 Safari,主要看中了它的高效能,因此我可以打开一大堆标签页。 而在 Windows 上,Edge(旧版)可以很好的贴合我的需求。 关于 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 
    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。但无论你使用什么,配置方法都是一样的。 服务启动的二进制程序应当设置为 -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 中,我们可以通过直接调用 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

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

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

发表回复

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