Lima: Linux virtual machines 配置与 Vs code 远程开发
Lima: Linux virtual machines
TL;DR
接上文:开发环境从 windows 切换至 macOS 之后,因为开发跨平应用的需求,我物色了一下 macOS 上的虚拟机软件。最终选择了 lima.
但是真的也遇到相当的问题。
- 虚拟机预配置
- 虚拟机的 ssh
- 虚拟机的 checkpoint/snapshot 快照功能
- 虚拟机的网络配置
- ...
Lima 简单介绍
Lima 是一款专为 macOS 设计的轻量级 Linux 虚拟机工具,通过 QEMU 和 macOS 原生虚拟化技术实现,旨在简化在 Mac 上运行 Linux 环境和容器的流程。以下是其核心特点的简要说明:
一、核心功能
- 虚拟机管理
提供开箱即用的 Linux 虚拟机环境,支持快速创建和管理虚拟机,无需复杂配置即可运行各类 Linux 应用和工具。 - 容器运行支持
专门优化容器运行场景,帮助克服 macOS 上直接运行容器的兼容性问题,类似 Windows 的 WSL2 体验。 - 文件共享与端口转发
内置主机与虚拟机间的文件共享机制,并支持端口转发,方便开发调试和网络服务访问。
二、技术优势
- 轻量化设计
基于 QEMU 和 macOS 原生虚拟化框架 (vz),资源占用低,启动速度快。 - 开发友好
针对开发者需求优化,支持 Docker 等容器工具,无缝集成 macOS 开发环境。
三、应用场景
适用于 macOS 用户需要 Linux 环境的场景,如跨平台开发、容器化应用测试、Linux 工具链使用等,尤其适合需要替代 Docker Desktop 的开发者。
我的虚拟机选型过程
一、核心需求
- 跨平台开发环境: 需要一个能在 MacBook Air 上同时进行 macOS 和 Linux 版本开发与测试的工作流。
- 功能强大的 Linux 环境: 这个 Linux 环境必须能够支持底层的网络操作,具体包括:
- 创建和管理 TUN 虚拟网卡。
- 修改系统的路由表以实现“全流量导入”。
- 操作
iptables
或nftables
防火墙规则,以实现“按规则放行”的策略路由。
- 高效的开发工作流: 我强烈需要一个能够快速重置的开发环境。具体来说,我希望避免在每次环境出问题后,都花费数分钟重新安装依赖和进行配置。我理想的工作流是:
- 一次性完成环境的复杂配置(安装依赖、配置 SSH 等)。
- 将这个配置好的状态保存为一个 “基础检查点” (Base-Checkpoint)。
- 在后续开发测试中,如果环境被改动或损坏,能够瞬时(秒级)回滚到这个干净的“基础检查点”,然后继续工作。
二、目标
我希望的 VM Linux 环境如下:
- 系统:Ubuntu 22.04 arm64 (但是我其实也会去尝试测试 x86_64)
- 预配置:git, rust 1.85 , tmux, fish shell (并且设置为默认 shell),
- 预配置:通过 uv 包管理器安装的 pre-commit, tmuxp (uv tool install …)
- 预配置:还有 cargo 安装的 taplo-cli,
- 预配置:ssh allow pubkey: ssh-ed25519 xxxxxxxxxxxx
- 预配置:需要 mounts 共享的路径:~/dev
- 预配置:网络接口为 bridge, 从而能够共享 Host 的网卡访问外网
Lima 配置
直接上完整,正确配置,然后再说明在配置过程中遇到的坑点,以及解决办法吧
lima-base.yaml
# lima-base.yaml — 用于构建基础镜像(不包含 host mounts)
arch: "aarch64"
vmType: "qemu" # snapshot only works on qemu
mountType: "9p"
mountInotify: true
cpus: 8
memory: "12GiB"
disk: "40G"
images:
- location: "https://cloud-images.ubuntu.com/releases/jammy/release/ubuntu-22.04-server-cloudimg-arm64.img"
arch: "aarch64"
# NOTE: mounts intentionally removed for the base image so snapshots succeed.
# 虚拟机启动时会执行的脚本
provision:
- mode: system
script: |
#!/usr/bin/env bash
set -eux
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get upgrade -y
# Install system-level packages (fish correctly listed; remove stray tokens)
apt-get install -y --no-install-recommends \
build-essential curl ca-certificates git tmux fish \
iproute2 iptables nftables tcpdump jq rsync sudo \
python3 python3-pip
# Make sure default user (Lima usually creates a user named same as host) is in sudoers
# (Lima typically makes that user sudo-enabled by default; we ensure sudo is available)
if ! grep -q '^%sudo' /etc/sudoers 2>/dev/null; then
echo '%sudo ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/99-sudo-nopass
chmod 440 /etc/sudoers.d/99-sudo-nopass
fi
# Enable IPv4 forwarding (persistent)
sysctl -w net.ipv4.ip_forward=1
if ! grep -q '^net.ipv4.ip_forward' /etc/sysctl.conf 2>/dev/null; then
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
fi
# Add fish to /etc/shells if not already there
if ! grep -q '/usr/bin/fish' /etc/shells; then
echo '/usr/bin/fish' >> /etc/shells
fi
apt-get clean
rm -rf /var/lib/apt/lists/*
- mode: user
script: |
#!/usr/bin/env bash
set -eux
# Ensure PATH includes common local bins immediately for the rest of the script
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
# Install rustup & Rust 1.85 for the current user
if ! command -v rustup >/dev/null 2>&1; then
curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
fi
# Ensure cargo in PATH for this script
export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH"
# Install toolchain 1.85 (idempotent)
if command -v rustup >/dev/null 2>&1; then
~/.cargo/bin/rustup toolchain install 1.85.0 || true
~/.cargo/bin/rustup default 1.85.0 || true
fi
# Install taplo-cli via cargo (idempotent)
if command -v cargo >/dev/null 2>&1; then
~/.cargo/bin/cargo install taplo-cli || true
fi
# Install uv (Astral) into ~/.local/bin (official recommended installer)
# Use explicit PATH so we can call uv immediately afterwards
curl -LsSf https://astral.sh/uv/install.sh | sh
# Ensure uv is available via explicit path (installer defaults to ~/.local/bin)
export PATH="$HOME/.local/bin:$PATH"
# Install pre-commit and tmuxp via uv
# use explicit full path to uv to avoid PATH issues
if [ -x "$HOME/.local/bin/uv" ]; then
"$HOME/.local/bin/uv" tool install pre-commit --with pre-commit-uv --force-reinstall || true
# tmuxp is a pip package; install via uv tool also right now
"$HOME/.local/bin/uv" tool install tmuxp || true
fi
# Ensure fish has the right PATH lines (so interactive fish sees cargo & local bin)
mkdir -p ~/.config/fish
cat >> ~/.config/fish/config.fish <<'FISHCFG'
# added by lima provision: include cargo and local bin
set -gx PATH $HOME/.cargo/bin $HOME/.local/bin $PATH
FISHCFG
# Create fish functions directory
mkdir -p ~/.config/fish/functions
# Create rsync_work function
cat > ~/.config/fish/functions/rsync_work.fish <<'RSYNC_FUNC'
function rsync_work -d "Sync work directory with smart excludes and progress"
# Default values
set -l default_src "[mount 进来的路径]/dev"
set -l default_dst "$HOME/dev_local"
set -l default_excludes "target" ".git" "node_modules" ".DS_Store" "*.tmp" "*.log" ".env" ".vscode" ".idea" "dist" "build" "__pycache__" "*.pyc" ".pytest_cache" ".coverage" "coverage.xml"
# Parse arguments
set -l src ""
set -l dst ""
set -l excludes $default_excludes
set -l dry_run false
set -l verbose false
set -l help false
# Parse command line options
for i in (seq (count $argv))
switch $argv[$i]
case -h --help
set help true
case -n --dry-run
set dry_run true
case -v --verbose
set verbose true
case --src
if test (math $i + 1) -le (count $argv)
set src $argv[(math $i + 1)]
end
case --dst
if test (math $i + 1) -le (count $argv)
set dst $argv[(math $i + 1)]
end
case --exclude
if test (math $i + 1) -le (count $argv)
set -a excludes $argv[(math $i + 1)]
end
case '*'
# Positional arguments: src dst
if test -z "$src"
set src $argv[$i]
else if test -z "$dst"
set dst $argv[$i]
end
end
end
# Show help
if test $help = true
echo "Usage: rsync_work [OPTIONS] [SRC] [DST]"
echo ""
echo "Sync work directory with smart excludes and progress"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -n, --dry-run Show what would be transferred without actually doing it"
echo " -v, --verbose Verbose output"
echo " --src PATH Source directory (default: $default_src)"
echo " --dst PATH Destination directory (default: $default_dst)"
echo " --exclude PATTERN Add exclude pattern"
echo ""
echo "Examples:"
echo " rsync_work # Use defaults"
echo " rsync_work ~/project ~/backup # Sync specific dirs"
echo " rsync_work --dry-run # Preview changes"
echo " rsync_work --exclude '*.bak' --verbose # Custom exclude with verbose"
return 0
end
# Set defaults if not provided
if test -z "$src"
set src $default_src
end
if test -z "$dst"
set dst $default_dst
end
# Validate source directory
if not test -d "$src"
echo "Error: Source directory '$src' does not exist" >&2
return 1
end
# Create destination directory if it doesn't exist
if not test -d "$dst"
echo "Creating destination directory: $dst"
mkdir -p "$dst"
end
# Build rsync command arguments as an array
set -l rsync_args -a --delete --progress
# Add dry-run flag
if test $dry_run = true
set -a rsync_args --dry-run
echo "=== DRY RUN MODE - No files will be transferred ==="
end
# Add verbose flag
if test $verbose = true
set -a rsync_args --verbose
end
# Add exclude patterns (properly quoted to avoid globbing)
for exclude in $excludes
set -a rsync_args "--exclude=$exclude"
end
# Add source and destination
set -a rsync_args "$src/" "$dst/"
# Show what we're doing
echo "Syncing: $src -> $dst"
if test (count $excludes) -gt 0
echo "Excluding: "(string join ", " $excludes)
end
echo ""
# Execute rsync with proper argument handling
command rsync $rsync_args
set -l exit_code $status
if test $exit_code -eq 0
echo ""
if test $dry_run = true
echo "✓ Dry run completed successfully"
else
echo "✓ Sync completed successfully"
end
else
echo ""
echo "✗ Sync failed with exit code: $exit_code" >&2
return $exit_code
end
end
RSYNC_FUNC
# Install fisher (fish plugin manager) and plugins
# We need to run these commands in fish context
if [ -x /usr/bin/fish ]; then
# Install fisher plugin manager
/usr/bin/fish -c "curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher" || true
# Install z plugin for fast directory jumping
/usr/bin/fish -c "fisher install jethrokuan/z" || true
fi
# Change default shell to fish for current user (requires sudo)
# First verify fish is installed and in /etc/shells
if [ -x /usr/bin/fish ]; then
# Check if fish is already the default shell
if [ "$(getent passwd $(whoami) | cut -d: -f7)" != "/usr/bin/fish" ]; then
echo "Changing default shell to fish..."
sudo chsh -s /usr/bin/fish "$(whoami)"
echo "Default shell changed to fish. You may need to log out and back in for this to take effect."
else
echo "Fish is already the default shell."
fi
else
echo "Warning: fish shell not found at /usr/bin/fish"
fi
ssh:
loadDotSSHPubKeys: true
坑点说明:
https://github.com/lima-vm/lima/blob/master/templates/default.yaml
Lima 的默认配置模板。推荐仔细阅读。
-
lima 的配置文件是可以预配置 SSH 的,但是并不支持指定 ssh 的预配置内容
ssh: loadDotSSHPubKeys: true # 而是将 Host 的 ~/.ssh/*.pub 都给加入到 lima vm 的 ~/.ssh/authorized_keys 中
-
lima 的 VM arch 应该使用和 Host 一致的 arch, 才能有更好的性能。
具体内容请查看:https://lima-vm.io/docs/config/multi-arch/ -
lima 的 snapshot 只支持
qemu
的 vmType.# snip ... vmType: "qemu" # snapshot only works on qemu # snip ...
在 macOS 上,使用 vz vmType, 速度会更快。
https://lima-vm.io/docs/config/vmtype/vz/ 中写有
“vz” option makes use of native virtualization support provided by macOS Virtualization.Framework.
但是因为我的 checkpoint/snapshot 需求,我只能将vmType
设置为qemu
但是实际使用下来,并没有感受到什么差距。 -
qemu
的 vmType 只能使用mountType: "9p"
的文件系统挂载类型。也就是virtfs
.
如果能够使用mountType: "virtiofs"
的话,性能会更好。
virtfs
性能能够从使用体感上就感知到相较于virtiofs
没那么快。
但是qemu
不支持virtiofs
. 故此作罢。
相关内容参考:https://lima-vm.io/docs/config/mount/The “virtiofs” mount type is implemented via the virtio-fs device by using apple Virtualization.Framework shared directory on macOS and virtiofsd on Linux.
-
limactl snapshot
的使用方法为:limactl snapshot create devvm-base --tag golden-v1
其中 --tag 则是本次 checkpoint/snapshot 的 name. 而 create 后的 devvm-base 则是当前创建好的 lima vm 的 name.
创建成功后,可以通过limactl snapshot list devvm-base
列出所有基于devvm-base
vm 创建的所有 snapshots. -
使用
limactl snapshot
创建快照时,
其指定的 vm 状态必须为stop
:limactl stop devvm-base
或者强制关闭limactl stop --force devvm-base
-
limactl snapshot create <vm-name> --tag <snapshot-name>
执行的 vm 对象,不能够包含有 mounts.
这也就是为什么我给出的 lima-base.yaml 文件中没有 mounts 配置的原因。
而我的解决办法,则是将 base vm 和 work vm 拆分为两阶段构建。- base vm 则是上面的 lima-base.yaml 文件构建出来的 vm
- work vm 则是基于 base-vm clone, edit 之后的 vm.
- 详情见后续
两阶段构建
此处才是本文最核心的地方。
解决方案:分离“基础镜像”和“工作环境”: 这是一种标准的“黄金镜像 (Golden Image)”工作流程,可以完美解决我定制话 work-vm 并且快速恢复问题。
思路是:
1.创建一个不带 mounts 的纯净基础模板 (lima-base.yaml -> devvm-base),对它进行耗时的 provision 并制作快照。
2. 然后,从这个快照启动你的日常工作环境,并仅在此时加入 mounts 配置 (clone devvm-base -> devvm)。
步骤 1:创建 lima-base.yaml
(用于构建基础镜像)
创建 lima-base.yaml
。
# lima-base.yaml — 用于构建可快照的、纯净的基础镜像
arch: "aarch64"
vmType: "qemu"
mountType: "9p"
# mountInotify: true # 当没有 mounts 时,此项无意义
cpus: 8
memory: "12GiB"
disk: "40G"
images:
- location: "https://cloud-images.ubuntu.com/releases/jammy/release/ubuntu-22.04-server-cloudimg-arm64.img"
arch: "aarch64"
# 关键:删除或注释掉整个 mounts 部分
# mounts:
# - location: "~/dev"
# writable: true
provision:
# ... 你的所有 provision 脚本保持不变 ...
- mode: system
script: |
#!/usr/bin/env bash
# ... (内容不变)
- mode: user
script: |
#!/usr/bin/env bash
# ... (内容不变)
ssh:
loadDotSSHPubKeys: true
步骤 2:构建基础镜像并创建快照(一次性操作)
现在,用这个 base
配置走一遍那个漫长的过程,但你只需要做一次。
-
创建 base 实例:
limactl create --name=devvm-base ./lima-base.yaml
-
启动并等待它完成所有 provisioning (就是执行定制化配置 linux 环境的那些脚本):
limactl start devvm-base
-
停止实例:
limactl stop devvm-base
-
创建快照:
limactl snapshot create devvm-base --tag=golden-v1
因为这个
devvm-base
实例没有任何mounts
,这个命令会成功执行。你现在拥有了一个包含了所有软件和配置的“黄金快照”。
步骤 3:Clone devvm-base 为 devvm (用于日常工作)
-
挂载 host 目录
limactl clone devvm-base devvm # clone devvm-base as devvm limactl edit devvm --mount "~/dev:w" # 将 Host 的 ~/dev 目录挂载到 devvm 的 ~/dev 目录
-
更改网络接口为 bridged
limactl edit devvm --network lima:bridged # 使用 bridged 网络 # 安装 socket_vmnet: https://lima-vm.io/docs/config/network/vmnet/ # 改成 bridged 之后就必须安装 socket_vmnet VERSION="$(curl -fsSL https://api.github.com/repos/lima-vm/socket_vmnet/releases/latest | jq -r .tag_name)" FILE="socket_vmnet-${VERSION:1}-$(uname -m).tar.gz" # Download the binary archive curl -OSL "https://github.com/lima-vm/socket_vmnet/releases/download/${VERSION}/${FILE}" # (Optional) Preview the contents of the binary archive tar tzvf "${FILE}" # Install /opt/socket_vmnet from the binary archive sudo tar Cxzvf / "${FILE}" opt/socket_vmnet sudoers >etc_sudoers.d_lima && sudo install -o root etc_sudoers.d_lima "/private/etc/sudoers.d/lima"
-
启动虚拟机 (optional): 这一步就是为了检查虚拟机是否正常启动,如果启动失败,请检查错误信息。
limactl start devvm
-
创建 snapshot (optional):
limactl snapshot create devvm --tag devvm-v1 limactl snapshot list devvm
步骤 4:享受你的极速工作流
现在,你的日常操作变成了:
-
启动工作实例:
limactl start devvm # 这个过程也会非常快,因为它跳过了所有 provision INFO[0000] Using the existing instance "devvm" INFO[0000] Starting socket_vmnet daemon for "bridged" network INFO[0000] Starting the instance "devvm" with VM driver "qemu" INFO[0000] QEMU binary "/opt/homebrew/bin/qemu-system-aarch64" seems properly signed with the "com. apple.security.hypervisor" entitlement INFO[0000] [hostagent] Using system firmware ("/opt/homebrew/share/qemu/edk2-aarch64-code.fd") INFO[0000] [hostagent] Starting QEMU (hint: to watch the boot progress, see "~/.lima/devvm/serial*.log") INFO[0001] SSH Local Port: 60420 INFO[0000] [hostagent] Waiting for the essential requirement 1 of 2: "ssh" INFO[0010] [hostagent] Waiting for the essential requirement 1 of 2: "ssh" INFO[0020] [hostagent] Waiting for the essential requirement 1 of 2: "ssh" INFO[0031] [hostagent] Waiting for the essential requirement 1 of 2: "ssh" INFO[0041] [hostagent] Waiting for the essential requirement 1 of 2: "ssh" INFO[0051] [hostagent] Waiting for the essential requirement 1 of 2: "ssh" # 但是当前配置的文件写得有点问题,所以会出现 hang: Waiting for the essential requirement 1 of 2: "ssh" # 我们可以不用管,直接新开一个 terminal, 然后输入 limactl shell devvm 即可进入 devvm 中了。
现在你的 devvm
会在几秒内启动完毕,并且 ~/dev
目录也已经自动挂载好了,网络也变成了 bridged。
当你需要一个全新的、纯净的环境时,只需 limactl delete devvm
,然后重新 clone devvm-base
, 并且 edit
创建即可,整个过程都会非常迅速。
这个方法将一次性的长时间构建和日常的快速启动完美分离开。
增强 SSH 体验
目前的 lima-base.yaml 中并没有配置 vm 的 ssh port, 那么按照 lima 的默认配置,ssh port 则是会自动随机获取一个。
比如: INFO[0001] SSH Local Port: 60420
就是随机获取到了 60420 端口。
具体内容请参考:https://github.com/lima-vm/lima/blob/master/templates/default.yaml 的 SSH 配置部分。
而我这不打算使用固定的 ssh port 配置,而是保持默认。
当我们不想使用 limactl shell 的方式来连接 vm 时,我们可以使用下面的方式来直接使用 ssh 连接:
ssh -F ~/.lima/devvm/ssh.config lima-devvm
# 效果等同: limactl shell devvm
- 为什么可以使用这样的语法来连接呢?
~/.lima/devvm/ssh.config
文件又是什么?
相信你已经猜到了,
lima-devvm
是我们创建的 vm 的名称,但是由 lima 加了一个lima-
前缀作为标识。~/.lima/devvm/ssh.config
是 lima 自动创建并且自动更新的 ssh 配置文件。
那么上面的 ssh 命令的语意也就十分明确:通过 ssh -F
指定 ssh 配置文件,然后通过 lima-devvm
指定 vm 的名称 (在指定的 ssh 配置文件中寻找对应的 ssh 配置).
~/.lima/devvm/ssh.config
文件内容如下:
cat ~/.lima/devvm/ssh.config lima-devvm
# This SSH config file can be passed to 'ssh -F'.
# This file is created by Lima, but not used by Lima itself currently.
# Modifications to this file will be lost on restarting the Lima instance.
Host lima-devvm
IdentityFile "~/.ssh/id_ed25519"
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
NoHostAuthenticationForLocalhost yes
PreferredAuthentications publickey
Compression no
BatchMode yes
IdentitiesOnly yes
GSSAPIAuthentication no
Ciphers "^aes128-gcm@openssh.com,aes256-gcm@openssh.com"
User user
ControlMaster auto
ControlPath "~/.lima/devvm/ssh.sock"
ControlPersist yes
Hostname 127.0.0.1
Port 60420 # 这里的 port 是 Lima-devvmv 运行时自动获取的 ssh port, 并且由 lima 自动更新
既然 lima 存在一个由它自己创建并且维护的 ssh config.
那么我们可以在 ~/.ssh/config
中使用引用 lima 的 ssh config 配置的方式来简化 SSH 指令了。
现在去编辑 ~/.ssh/config
文件,在文件的最开始添加如下内容:
# 引用 lima 的 ssh config
Include ~/.lima/devvm/ssh.config
添加之后的 ~/.ssh/config
格式如下:
cat ~/.ssh/config
# Added by OrbStack: 'orb' SSH host for Linux machines
# This only works if it's at the top of ssh_config (before any Host blocks).
# This won't be added again if you remove it.
Include ~/.orbstack/ssh/config
# 引用 lima 的 ssh config
Include ~/.lima/devvm/ssh.config
Host 192.168.1.99
Port 22
User user
IdentityFile ~/.ssh/id_ed25519
更新完 ~/.ssh/config 文件后。我们就可以使用下面的指令来连接 lima 的虚拟机了。
ssh lima-devvm
这里的 ssh 就会
- 去 ~/.ssh/config 文件中寻找 Host 叫做 lima-devvm 的配置
- 发现有引用 ~/.lima/devvm/ssh.config 文件
- 在 ~/.lima/devvm/ssh.config 文件中寻找 Host 叫做 lima-devvm 的配置
- 找到配置,并使用
- 登录成功
至此,我们就可以在安装了 lima 的机器上使用 ssh 登录 lima-devvm 了。
远程连接 Host 中的 lima-devvm
在继续讲解之前,我需要将基本的概念先介绍下:
- Host:lima-devvm 所在的机器 (记作
mac-remote
) - Host 中的 lima-devvm: 即 mac-remote 机器上通过 lima 创建的 vm (记作
mac-remote-lima-vm
) - 远程连接 Host: 即我现在并没有使用 mac-remote 这个机器,而是新开一个全新的机器,并且通过 ssh 的方式连接 mac-remote. 这台全新的机器记作
windows/linux/macOS
.
那么我们如何通过 ssh "直接"连接到 mac-remote-lima-vm 呢?
按逻辑来讲,我们需要先通过 ssh 连接到 mac-remote, 然后再通过 ssh 连接到 mac-remote-lima-vm.
但是这样的动作十分繁琐,并且我们没有办法利用上 VS code ssh 远程开发的功能 (重点).
我们要连接到 mac-remote-lima-vm 的确是需要 mac-remote 的,但是我们可以通过 ssh 创建一个代理,然后通过代理连接到 mac-remote-lima-vm. 即
windows/linux/macOS
的 ~/.ssh/config
内容如下
Host mac-remote
HostName [mac-romote 的 IP]
User [mac-remote 的用户名]
IdentityFile ~/.ssh/id_ed25519
# 保持连接,避免频繁断开 (可选)
ServerAliveInterval 60
# 在你的本地机器上修改 ~/.ssh/config
Host mac-remote-lima-vm
# 直接指定最终目标在远程服务器上的真实 IP 和端口
HostName 127.0.0.1
Port 64143
# 这里的端口就需要手动更新了, 使其匹配上你想要连接到的 mac-remote-lima-vm 的端口
User user
# 使用 ProxyCommand 代替 ProxyJump
# 它会先连接到 remote-dev-server,然后让它作为管道连接到上面的 HostName 和 Port
ProxyCommand ssh -i ~/.ssh/id_ed25519 remote-dev-server -W %h:%p
ForwardAgent yes
IdentityFile ~/.ssh/id_ed25519
将 windows/linux/macOS
的 ~/.ssh/config
更新之后。
我们即可在 windows/linux/macOS
的 terminal 中使用 ssh mac-remote-lima-vm
登录 mac-remote
中的 lima vm。
当然 VS code 也是可以的。从而获得了更佳的 VS code 开发体验。