# Tailscale + SSH 远程开发操作指南

## 文档概述

Mac 电脑通过 Tailscale 跨网络 SSH 连接 Windows 电脑上的 Linux 虚拟机（VMware），实现远程 Android 底层开发。

## 当前环境信息（2026-04-17 已验证）

| 项目 | 信息 |
|------|------|
| Mac 电脑 | macOS, ClashX Pro (VPN), Tailscale |
| Windows 电脑 | VMware Workstation 17, Clash for Windows v0.19.10 |
| Linux 虚拟机 | Ubuntu 20.04 LTS, VMware 桥接模式 |
| 虚拟机用户 | zhangwenqi |
| Tailscale IP | 100.123.82.91 |
| 虚拟机局域网 IP | 192.168.6.60 |
| SSH 密钥 | Mac ~/.ssh/id_ed25519 已部署到虚拟机 |
| Docker | 28.1.1 |
| **Radxa CM5 环境** | Docker 镜像 android-builder:12.x (6.48GB, Ubuntu 20.04 基底)，SDK 已编译验证 |
| **OrangePi CM5 环境** | Docker 镜像 android-builder-orangepi:13.x (待构建, Ubuntu 22.04 基底)，SDK 已合并待解压 |
| 磁盘 | 700GB 虚拟磁盘，剩余空间足够双环境并行 |

---

# 第一部分：网络架构

## 1.1 整体架构

```
Mac (ClashX Pro VPN + Tailscale)
  |
  | Tailscale P2P (100.x.x.x)
  |
Linux 虚拟机 (Tailscale, IP: 100.123.82.91)
  |
  | VMware 桥接 (192.168.6.60)
  |
Windows (Clash for Windows, 端口 7890)
```

## 1.2 各设备职责

- Mac: 开发主机，VPN 翻墙 + SSH 远程开发
- Windows: 宿主机，运行 VMware 虚拟机 + Clash 代理
- Linux 虚拟机: 编译服务器，Docker + Android SDK 编译环境

---

# 第二部分：ClashX Pro 与 Tailscale 共存配置（关键）

Mac 上 ClashX Pro（增强模式/TUN）与 Tailscale 存在两个冲突，必须同时解决。

## 2.1 问题一：增强模式流量劫持

ClashX Pro 增强模式创建 TUN 虚拟网卡拦截所有流量，Tailscale 的 WireGuard UDP 流量也被劫持，导致 Tailscale 连不上。

**解决方法：** 在 ClashX Pro 代理配置文件的 rules 最前面添加直连规则。

配置文件路径: `~/.config/clash/你的订阅配置.yaml`

在 `rules:` 下方、第一条规则之前插入：

```yaml
rules:
 - PROCESS-NAME,tailscaled,DIRECT
 - IP-CIDR,100.64.0.0/10,DIRECT,no-resolve
 - IP-CIDR6,fd7a:115c:a1e0::/48,DIRECT,no-resolve
 - DOMAIN-SUFFIX,tailscale.com,DIRECT
 - DOMAIN-SUFFIX,login.tailscale.com,DIRECT
 # ... 原有规则保持不变 ...
```

各规则作用：
- `PROCESS-NAME,tailscaled,DIRECT` → Tailscale 进程所有流量直连（最关键）
- `IP-CIDR,100.64.0.0/10,DIRECT` → Tailscale 内网 IP 段直连
- `IP-CIDR6,fd7a:115c:a1e0::/48,DIRECT` → Tailscale IPv6 段直连
- `DOMAIN-SUFFIX,tailscale.com,DIRECT` → Tailscale 控制服务器直连
- `DOMAIN-SUFFIX,login.tailscale.com,DIRECT` → Tailscale 认证直连

## 2.2 问题二：MagicDNS 劫持域名解析

Tailscale 默认开启 MagicDNS，将系统 DNS 改为 100.100.100.100，与 ClashX Pro 增强模式的 fake-ip DNS（198.18.0.2）冲突。

典型表现：浏览器能访问 YouTube，但 Claude Code 等终端应用无法连接。

**解决方法：** 关闭 Tailscale DNS 接管。

操作步骤：
1. 点击 Mac 菜单栏 Tailscale 图标
2. 进入 Preferences（偏好设置）
3. 找到 "Use Tailscale DNS Settings" → 关闭

或通过管理后台：浏览器打开 https://login.tailscale.com/admin/dns → 关闭 MagicDNS

## 2.3 订阅更新覆盖问题

ClashX Pro 订阅更新会覆盖配置文件，Tailscale 直连规则会丢失。

**应对：** 每次订阅更新后，重新在 `rules:` 下插入 2.1 中的 5 条规则，然后 ClashX Pro 菜单 → 配置 → 重新加载配置。

备份规则片段方便粘贴：
```
 - PROCESS-NAME,tailscaled,DIRECT
 - IP-CIDR,100.64.0.0/10,DIRECT,no-resolve
 - IP-CIDR6,fd7a:115c:a1e0::/48,DIRECT,no-resolve
 - DOMAIN-SUFFIX,tailscale.com,DIRECT
 - DOMAIN-SUFFIX,login.tailscale.com,DIRECT
```

## 2.4 验证清单

1. 开启 ClashX Pro 增强模式 + 选择代理节点
2. 开启 Tailscale
3. 浏览器访问 YouTube → VPN 正常
4. 终端 `ssh zhangwenqi@100.123.82.91` → Tailscale SSH 正常
5. Claude Code 对话 → DNS 解析正常

失败排查：
- 第 3 步失败 → 检查代理节点
- 第 4 步失败 → 检查 rules 中 Tailscale 直连规则
- 第 5 步失败 → 检查 Tailscale DNS Settings 是否已关闭

---

# 第三部分：Linux 虚拟机环境配置

## 3.1 Tailscale 安装

```bash
# 安装
curl -fsSL https://tailscale.com/install.sh | sh

# 启动并认证（浏览器打开输出的链接登录）
sudo tailscale up

# 查看分配的 IP
tailscale ip -4
```

## 3.2 SSH 服务

```bash
# 安装（如未安装）
sudo apt update && sudo apt install openssh-server -y

# 启动
sudo systemctl enable --now ssh

# 检查状态
sudo systemctl status ssh
```

## 3.3 Docker 安装

```bash
# 添加 Docker 清华镜像源
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu focal stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装
sudo apt-get update -y
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 当前用户加入 docker 组（免 sudo）
sudo usermod -aG docker $USER
# 需要重新登录生效
```

## 3.4 Docker 镜像加速（国内必须配置）

Docker Hub 在国内无法直接访问，需要配置镜像加速器：

```bash
sudo tee /etc/docker/daemon.json > /dev/null << 'EOF'
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.xuanyuan.me",
    "https://docker.m.daocloud.io"
  ]
}
EOF
sudo systemctl daemon-reload && sudo systemctl restart docker
```

注意：镜像加速器地址可能失效，届时搜索"Docker 镜像加速 2026"获取最新可用地址。

## 3.5 构建 Android 12 编译镜像

Dockerfile 位于虚拟机 `~/Radxa_CM5/docker/Dockerfile`，内容基于 Radxa 官方文档，使用清华镜像源。

```bash
cd ~/Radxa_CM5/docker
sudo docker build -t android-builder:12.x .
```

构建完成验证：
```bash
sudo docker run --rm android-builder:12.x bash -c \
  'java -version 2>&1; python3 --version; gcc --version | head -1; which repo'
```

预期输出：
- Java: openjdk 1.8.0
- Python: 3.8.x
- GCC: 9.4.0
- Repo: /usr/local/bin/repo

### 与官方 Dockerfile 的差异（重要）

当前镜像**缺少官方 Dockerfile 中的 5 套 Linaro 交叉编译工具链 COPY**：

```
gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf   → /opt/ 和 /opt/toolchains/
gcc-linaro-aarch64-none-elf-4.8-2013.11_linux          → /opt/ 和 /opt/toolchains/
gcc-arm-none-eabi-6-2017-q2-update                     → /opt/toolchains/
gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu      → /opt/toolchains/
gcc-linaro-7.2.1-2017.11-x86_64_aarch64-elf            → /opt/toolchains/
```

**原因：** 这些工具链需要单独下载（官方文档未提供下载地址），放到 Dockerfile 同目录才能 COPY。

**影响评估：**
- Radxa Android SDK 的 `prebuilts/` 目录自带编译所需工具链
- 编译脚本 `build.sh` 优先使用 SDK 内的工具链
- 这些 Linaro 工具链主要给 U-Boot 和 kernel **单独编译**用

**编译时如果报错找不到工具链，按以下方式排查修复：**

1. 确认报错中缺少的具体工具链名称
2. 搜索 Linaro 官网下载对应版本：https://releases.linaro.org/components/toolchain/binaries/
3. 解压到 Docker 容器的 `/opt/toolchains/` 目录：
   ```bash
   # 在容器内
   cd /opt/toolchains
   wget <工具链下载地址>
   tar -xf <工具链压缩包>
   ```
4. 或者将工具链放到 `~/Radxa_CM5/docker/` 目录，取消 Dockerfile 中 COPY 行注释后重新构建镜像

## 3.6 Radxa CM5 Android 12 编译环境（基于 Ubuntu 20.04）

### 3.6.1 解压 SDK

```bash
cd ~/Radxa_CM5/SDK_Android
tar -xzf radxa_android_sdk_backup.tar.gz
# 解压后目录为 radxa-android-sdk/，耗时视磁盘速度而定
```

### 3.6.2 编译前必须修复的问题（重要）

SDK 存在两个已知问题，必须在编译前修复，否则编译会失败。

**问题一：GMS 引用不存在（lunch 阶段报错）**

错误信息：`vendor/partner_gms/products/gms.mk does not exist`

原因：RadxaCM5.mk 引用了 Google GMS 包（Google Play 服务），但开源 SDK 不包含 GMS（需 Google 授权）。

修复：注释掉 GMS 引用行。

```bash
cd ~/Radxa_CM5/SDK_Android/radxa-android-sdk
sed -i 's/$(call inherit-product, vendor\/partner_gms\/products\/gms.mk)/#$(call inherit-product, vendor\/partner_gms\/products\/gms.mk)/' device/rockchip/rk3588/RadxaCM5/RadxaCM5.mk
```

验证：
```bash
grep "gms" device/rockchip/rk3588/RadxaCM5/RadxaCM5.mk
# 应看到该行前面有 # 注释符
```

注意：注释 GMS 后编译出的系统不含 Google Play 商店和 Google 服务，其他功能不受影响。

**问题二：缺少 RadxaCM5.config 内核配置文件（kernel 编译阶段报错）**

错误信息：`No configuration exists for this target on this architecture - RadxaCM5.config`

原因：BoardConfig.mk 指定 `PRODUCT_KERNEL_CONFIG := rockchip_defconfig android-11.config RadxaCM5.config`，但 `kernel-5.10/kernel/configs/` 目录下没有 `RadxaCM5.config` 文件。

修复：参考 rock5b.config 创建 CM5 内核配置文件。

```bash
cat > kernel-5.10/kernel/configs/RadxaCM5.config << 'EOF'
CONFIG_SND_SOC_ES8316=y
CONFIG_RTL8852BE=m
CONFIG_BT_RTKBTUSB=y
CONFIG_SENSORS_PWM_FAN=y
CONFIG_TOUCHSCREEN_GT9XX=y
CONFIG_TOUCHSCREEN_FTS=y
CONFIG_TOUCHSCREEN_FTS_RADXA=y
CONFIG_DRM_PANEL_RADXA=y
CONFIG_DRM_PANEL_RASPBERRYPI_TC358762=y
CONFIG_ROCKPI_MCU=y
CONFIG_VIDEO_IMX219=y
EOF
```

### 3.6.3 编译前增大 Swap（防止 OOM，重要）

Android 全量编译内存峰值可超过 20GB，虚拟机默认 Swap 仅 1GB，编译到 99% 时会因内存不足被 OOM Killer 杀死进程。

```bash
# 创建 8GB Swap 文件
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# 写入 fstab 永久生效
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# 验证（应显示 9GB 左右 Swap）
free -h | grep -i swap
```

同时建议 VMware 虚拟机内存分配 **16GB 以上**（20GB+ 更佳）。

### 3.6.4 进入 Docker 编译环境

```bash
# 注意挂载的是解压后的 radxa-android-sdk 目录
sudo docker run -it \
  --name android-build \
  -v ~/Radxa_CM5/SDK_Android/radxa-android-sdk:/workspace \
  -w /workspace \
  android-builder:12.x \
  bash
```

### 3.6.5 执行编译

```bash
# 在 Docker 容器内执行
source build/envsetup.sh
lunch RadxaCM5-userdebug

# 方法一：全量编译（推荐，约 5 小时）
# 如果虚拟机内存 ≤ 20GB，建议用 -j8 限制线程数减少内存峰值
./build.sh -AUCKup

# 方法二：如果 build.sh 因 OOM 中途失败，用 make 继续（会复用已编译缓存）
make -j8
```

编译参数说明：`-A` AOSP、`-U` U-Boot、`-C` Config、`-K` Kernel、`-u` update.img、`-p` 打包

编译过程中 `Build sandboxing disabled due to nsjail error` 是正常提示，Docker 容器内不支持 nsjail 沙箱，不影响编译。

### 3.6.6 编译后打包镜像

如果使用 `make -j8` 单独编译 AOSP（非 build.sh），需要手动打包：

```bash
# 在容器内执行
./mkimage.sh
```

### 3.6.7 验证编译结果

```bash
ls -lh rockdev/Image-RadxaCM5/
```

预期镜像文件：

| 文件 | 说明 |
|------|------|
| MiniLoaderAll.bin | Bootloader 加载器 |
| idbloader.img | IDB Loader |
| uboot.img | U-Boot |
| boot.img | Boot 镜像（内核+ramdisk） |
| recovery.img | Recovery 模式镜像 |
| super.img | 系统分区（system+vendor+odm，约 1.6GB） |
| dtbo.img | 设备树 Overlay |
| resource.img | 内核资源 |

### 3.6.8 退出和重新进入容器

```bash
# 退出容器
exit

# 重新进入已创建的容器
sudo docker start -ai android-build
```

### 3.6.9 烧录到开发板

编译完成的镜像在虚拟机 `~/Radxa_CM5/SDK_Android/radxa-android-sdk/rockdev/Image-RadxaCM5/` 目录。

拿到 CM5 开发板后：
1. 将镜像文件拷贝到 Windows
2. 使用 **RKDevTool**（瑞芯微官方烧录工具）烧录
3. 开发板进入 Loader 模式（按住 Recovery 按键上电）
4. 在 RKDevTool 中加载各 img 文件烧录

## 3.7 OrangePi CM5 Android 13 编译环境（基于 Ubuntu 22.04 Docker）

OrangePi CM5 Base（RK3588S）使用 Android 13 源码，与 Radxa CM5 的 Android 12 并行存在。
为避免污染主机环境，使用独立的 Docker 镜像（基于 Ubuntu 22.04，对齐 OrangePi 官方推荐环境）。

### 3.7.1 官方参考文档

- 《OrangePi_CM5_Base_RK3588S_用户手册_v1.3.pdf》第 7 章：Android 13 源码的编译方法
- 《OrangePi_CM5_Base_Tablet_RK3588S_用户手册_v1.0.pdf》

### 3.7.2 SDK 文件准备

OrangePi Android 13 SDK 来自官方百度云/谷歌网盘，是 9 个分卷压缩包：

```
Android_13.tar.gz00  (4.0GB)
Android_13.tar.gz01  (4.0GB)
...
Android_13.tar.gz08  (1.9GB)
md5sum              （校验文件）
```

存放路径：`~/OrangePi_CM5/Aandroid_OrangePi/`

### 3.7.3 MD5 校验

```bash
cd ~/OrangePi_CM5/Aandroid_OrangePi
md5sum -c md5sum
# 9 个文件全部显示"成功/确定"才能继续
```

### 3.7.4 合并压缩包（重要）

**经实测：官方文档的管道流式解压（`cat ... | tar -xzf -`）会在某个分卷边界出错**
（已解压 46GB 后报 `gzip: invalid compressed data--format violated`）。

**推荐做法：先合并成单个文件，再解压。**

```bash
cd ~/OrangePi_CM5/Aandroid_OrangePi
# 显式指定顺序合并（避免通配符顺序问题）
cat Android_13.tar.gz00 Android_13.tar.gz01 Android_13.tar.gz02 \
    Android_13.tar.gz03 Android_13.tar.gz04 Android_13.tar.gz05 \
    Android_13.tar.gz06 Android_13.tar.gz07 Android_13.tar.gz08 \
    > Android_13.tar.gz

# 合并后约 34GB，耗时约 15 分钟（SSD 速度约 37MB/s）
ls -lh Android_13.tar.gz
```

### 3.7.5 解压源码

```bash
# 解压到当前目录，产生 Android_13/ 目录
tar -xzf Android_13.tar.gz

# 解压后预计 80-100GB，耗时约 20-30 分钟
du -sh Android_13/
```

解压成功后可以删除合并文件和分卷文件节省空间：
```bash
# 可选：保留分卷和 md5sum 作为备份源，删除合并文件
rm Android_13.tar.gz

# 或：已验证解压成功后，彻底清理（需确认 Android_13/ 完整）
# rm Android_13.tar.gz Android_13.tar.gz0*
```

### 3.7.6 创建 Dockerfile

Dockerfile 位于虚拟机 `~/OrangePi_CM5/docker/Dockerfile`，基于 Ubuntu 22.04。

```dockerfile
FROM ubuntu:22.04

# 清华镜像源
RUN sed -i 's@archive.ubuntu.com@mirrors.tuna.tsinghua.edu.cn@g; \
    s@security.ubuntu.com@mirrors.tuna.tsinghua.edu.cn@g' /etc/apt/sources.list

ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 官方文档 7.2 要求的编译依赖
RUN apt-get update -y && apt-get install -y \
    git gnupg flex bison gperf build-essential \
    zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \
    lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev ccache \
    libgl1-mesa-dev libxml2-utils xsltproc unzip \
    && apt-get install -y u-boot-tools

# Android 13 需要 OpenJDK 11
RUN apt-get install -y openjdk-11-jdk

# 辅助工具
RUN apt-get install -y python3 python3-pip rsync cpio bc kmod \
    device-tree-compiler liblz4-tool lzop sudo vim nano

RUN apt-get clean && rm -rf /var/lib/apt/lists/*

WORKDIR /workspace
CMD ["/bin/bash"]
```

### 3.7.7 构建 Docker 镜像

```bash
cd ~/OrangePi_CM5/docker
sudo docker build -t android-builder-orangepi:13.x .
# 耗时约 5-10 分钟
```

验证镜像：
```bash
sudo docker images android-builder-orangepi
# 预期：TAG=13.x, SIZE 约 2GB
```

### 3.7.8 进入 Docker 编译环境

```bash
sudo docker run -it \
  --name orangepi-build \
  -v ~/OrangePi_CM5/Aandroid_OrangePi/Android_13:/workspace \
  -w /workspace \
  android-builder-orangepi:13.x \
  bash
```

### 3.7.9 执行编译

按官方文档 7.2 节编译参数：

| 参数 | 作用 |
|------|------|
| `-B` | 编译 U-Boot |
| `-K` | 编译 Kernel |
| `-a` | 编译 Android |
| `-F` | 编译 U-Boot + Kernel + Android（三合一） |
| `-M` | 在 rockdev 目录生成分区镜像 |
| `-u` | 打包生成最终 update.img |
| `-b` | 指定开发板型号 |
| `--gapps` | 包含 Google Play 服务（GApps） |

推荐全量编译命令：
```bash
# 容器内执行
./make.sh -FMu -b orangepicm5 --gapps
```

编译耗时：**约 3-6 小时**（取决于 CPU 核心数和内存）。

### 3.7.10 验证编译结果

成功后生成 `update.img`：

```bash
cd rockdev/Image-rk3588s_t
ls update.img
# 预期看到 update.img 文件
```

其他镜像文件也在 `rockdev/Image-rk3588s_t/` 目录。

### 3.7.11 烧录到开发板

1. 将 `update.img`（或其他镜像）拷贝到 Windows
2. 使用 **RKDevTool** 瑞芯微烧录工具
3. OrangePi CM5 进入 Loader 模式（按住 MaskROM/Recovery 键上电）
4. 加载 `update.img` 一键烧录

### 3.7.12 关键差异对比（与 Radxa CM5）

| 项目 | Radxa CM5 | OrangePi CM5 |
|------|-----------|--------------|
| Android 版本 | 12 | 13 |
| Docker 基础镜像 | ubuntu:20.04 | **ubuntu:22.04** |
| JDK 版本 | OpenJDK 8 | **OpenJDK 11** |
| 交叉编译工具链 | 需手动 COPY 5 套 Linaro | 编译脚本自动下载 |
| SDK 分发方式 | 单个压缩包 | 9 个分卷（需先合并） |
| 编译脚本 | `./build.sh -AUCKup` | `./make.sh -FMu -b orangepicm5 --gapps` |
| 镜像输出目录 | `rockdev/Image-RadxaCM5/` | `rockdev/Image-rk3588s_t/` |
| GMS 支持 | 需注释（官方不含） | `--gapps` 参数直接支持 |
| Docker 镜像名 | android-builder:12.x | android-builder-orangepi:13.x |

### 3.7.13 容器管理

```bash
# 退出容器（编译中断）
exit

# 重新进入已创建的容器（保留编译缓存）
sudo docker start -ai orangepi-build

# 删除容器（重新开始，会丢失容器内临时修改）
sudo docker rm -f orangepi-build
```

## 3.8 Linux 虚拟机共享 Windows VPN（外网访问）

虚拟机通过 VMware 桥接网络使用 Windows 的 Clash 代理上外网。已配置开机自动生效。

### 当前配置信息（2026-04-15 已验证）

| 项目 | 值 |
|------|-----|
| Windows 局域网 IP | 192.168.6.57 |
| Clash 代理端口 | 7890 |
| 虚拟机代理配置文件 | `/etc/profile.d/proxy.sh` |

### Windows 端配置（一次性）

1. Clash for Windows → 开启 **"允许局域网连接入Clash"**（Allow LAN）
2. Windows 防火墙放行 7890 端口（管理员 PowerShell 执行）：
   ```powershell
   netsh advfirewall firewall add rule name="Clash Proxy" dir=in action=allow protocol=tcp localport=7890
   ```

### Linux 虚拟机端配置（已完成，开机自动生效）

代理配置写入 `/etc/profile.d/proxy.sh`，每次登录自动加载：

```bash
export http_proxy="http://192.168.6.57:7890"
export https_proxy="http://192.168.6.57:7890"
export HTTP_PROXY="http://192.168.6.57:7890"
export HTTPS_PROXY="http://192.168.6.57:7890"
export no_proxy="localhost,127.0.0.1,100.64.0.0/10,192.168.0.0/16"
export NO_PROXY="localhost,127.0.0.1,100.64.0.0/10,192.168.0.0/16"
```

### 验证外网

```bash
curl -I https://www.google.com
curl -I https://releases.linaro.org/
```

### 前提条件

- Windows 电脑**开机且 Clash 运行中**
- Clash 的 "允许局域网连接入Clash" 保持开启

### 注意事项

- **Windows IP 变化**：如果 Windows 通过 DHCP 获取 IP，重启路由器后 IP 可能变化。届时需要修改 `/etc/profile.d/proxy.sh` 中的 IP 地址：
  ```bash
  sudo nano /etc/profile.d/proxy.sh
  # 将 192.168.6.57 替换为新的 Windows IP
  ```
  然后重新登录或执行 `source /etc/profile.d/proxy.sh` 生效。
  建议在 Windows 上设置静态 IP 或在路由器中绑定 DHCP 避免此问题。

- **Docker 容器内使用代理**：`/etc/profile.d/` 的代理对 Docker 容器内不生效，启动容器时需传入环境变量：
  ```bash
  sudo docker run -it \
    -e http_proxy="http://192.168.6.57:7890" \
    -e https_proxy="http://192.168.6.57:7890" \
    -e no_proxy="localhost,127.0.0.1" \
    --name android-build \
    -v ~/Radxa_CM5/SDK_Android:/workspace \
    -w /workspace \
    android-builder:12.x bash
  ```

---

# 第四部分：Mac 端 SSH 连接配置

## 4.1 生成 SSH 密钥

```bash
ssh-keygen -t ed25519 -C "mac-to-linux-vm"
# 一路回车使用默认设置
```

## 4.2 部署公钥到虚拟机

```bash
ssh-copy-id zhangwenqi@100.123.82.91
# 输入虚拟机密码，完成后即可免密登录
```

## 4.3 SSH 配置文件

编辑 `~/.ssh/config`：

```
Host linux-vm
    HostName 100.123.82.91
    User zhangwenqi
    Port 22
    ServerAliveInterval 30
    TCPKeepAlive yes
    ConnectTimeout 60
```

之后直接 `ssh linux-vm` 即可连接。

## 4.4 VS Code Remote-SSH

1. VS Code 安装 Remote - SSH 扩展
2. Cmd+Shift+P → "Remote-SSH: Connect to Host"
3. 选择 linux-vm
4. 连接成功后打开文件夹 `/home/zhangwenqi/Radxa_CM5`

---

# 第五部分：换新电脑配置指南

## 5.1 虚拟机备份说明

### 当前备份状态（2026-04-16）

| 项目 | 状态 |
|------|------|
| 备份格式 | OVF（.ovf + .vmdk + .mf 三个文件） |
| 备份内容 | 系统 + Docker（不含 SDK，不含工具链 COPY 到镜像） |
| Docker 镜像 | android-builder:12.x 基础环境已装，**未对齐官方工具链** |
| SDK 压缩包 | 单独存放在 Windows 电脑/移动硬盘 |

### OVF 备份包含的设置

| 配置项 | 是否备份 | 导入后能否修改 |
|--------|---------|---------------|
| 硬盘大小（700GB） | 包含 | 可扩大，不可缩小 |
| 内存大小 | 包含 | 可自由调整 |
| CPU 核心数 | 包含 | 可自由调整 |
| 网络适配器类型（桥接） | 包含 | 可改为 NAT/桥接/仅主机 |
| 操作系统和所有软件 | 包含 | — |

三个文件必须放在同一文件夹，缺一不可：
- `.ovf` — 虚拟机配置描述（CPU、内存、网卡等）
- `.vmdk` — 虚拟磁盘数据（系统和所有文件）
- `.mf` — 校验文件（验证完整性）

## 5.2 新电脑是 Windows — 完整恢复步骤

### 步骤 1：导入虚拟机

1. 新电脑安装 **VMware Workstation 17**（或更高版本）
2. VMware → 文件 → **打开** → 选择 `.ovf` 文件
3. 选择虚拟机存放位置（建议放在 SSD 上）
4. 等待导入完成
5. （可选）调整虚拟机参数：右键虚拟机 → 设置
   - 内存：建议 16GB+（Android 编译非常吃内存）
   - CPU：建议分配 8 核+
   - 网络：选择**桥接模式**（与宿主机同网段）

### 步骤 2：启动虚拟机并恢复网络

1. 启动虚拟机，登录（用户: zhangwenqi）

2. 检查网络：
   ```bash
   ip addr show    # 确认获取到 IP
   ping 8.8.8.8    # 测试内网连通性
   ```

3. 恢复 Tailscale：
   ```bash
   sudo systemctl status tailscaled    # 检查 Tailscale 服务
   sudo tailscale up                   # 重新认证（可能需要）
   tailscale ip -4                     # 记录新的 Tailscale IP
   ```

4. 更新代理配置（Windows IP 可能变化）：
   ```bash
   # 在 Windows cmd 执行 ipconfig 获取新的局域网 IP
   # 然后更新虚拟机代理配置
   sudo nano /etc/profile.d/proxy.sh
   # 将 192.168.6.57 替换为新 Windows 的局域网 IP
   source /etc/profile.d/proxy.sh
   
   # 测试外网
   curl -I https://www.google.com
   ```

### 步骤 3：恢复 SDK

```bash
# 将 SDK 压缩包从移动硬盘拷贝到虚拟机
# 可以通过 VMware 共享文件夹、scp、或 USB 挂载
cd ~/Radxa_CM5/SDK_Android/
# 确认文件
ls -lh radxa_android_sdk_backup.tar.gz
# 校验 MD5
md5sum radxa_android_sdk_backup.tar.gz
```

### 步骤 4：对齐官方 Dockerfile 环境（重要）

备份的虚拟机中 Docker 镜像是基础版本，**缺少 5 套 Linaro 交叉编译工具链**。需要重新下载工具链并重建镜像。

#### 4.1 确认 Docker 和基础镜像正常

```bash
docker images android-builder
# 预期看到 android-builder:12.x 镜像
```

#### 4.2 确认代理可用（下载工具链需要外网）

```bash
curl -I https://releases.linaro.org/
# 预期 HTTP 200
```

如果不通，先按 3.7 节配置虚拟机代理。

#### 4.3 下载 5 套交叉编译工具链

```bash
cd ~/Radxa_CM5/docker

# 1. gcc-linaro-6.3.1 arm-linux-gnueabihf（99MB）
curl -L -o gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf.tar.xz \
  'https://releases.linaro.org/components/toolchain/binaries/6.3-2017.02/arm-linux-gnueabihf/gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf.tar.xz'

# 2. gcc-linaro-aarch64-none-elf-4.8（50MB）
curl -L -o gcc-linaro-aarch64-none-elf-4.8-2013.11_linux.tar.bz2 \
  'https://releases.linaro.org/archive/13.11/components/toolchain/binaries/gcc-linaro-aarch64-none-elf-4.8-2013.11_linux.tar.bz2'

# 3. gcc-arm-none-eabi-6-2017-q2-update（96MB）
curl -L -o gcc-arm-none-eabi-6-2017-q2-update.tar.bz2 \
  'https://developer.arm.com/-/media/Files/downloads/gnu-rm/6-2017q2/gcc-arm-none-eabi-6-2017-q2-update-linux.tar.bz2'

# 4. gcc-linaro-6.3.1 aarch64-linux-gnu（106MB）
curl -L -o gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu.tar.xz \
  'https://releases.linaro.org/components/toolchain/binaries/6.3-2017.02/aarch64-linux-gnu/gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu.tar.xz'

# 5. gcc-linaro-7.2.1 aarch64-elf（51MB）
curl -L -o gcc-linaro-7.2.1-2017.11-x86_64_aarch64-elf.tar.xz \
  'https://releases.linaro.org/components/toolchain/binaries/7.2-2017.11/aarch64-elf/gcc-linaro-7.2.1-2017.11-x86_64_aarch64-elf.tar.xz'
```

#### 4.4 解压工具链

```bash
cd ~/Radxa_CM5/docker
tar -xf gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf.tar.xz
tar -xf gcc-linaro-aarch64-none-elf-4.8-2013.11_linux.tar.bz2
tar -xf gcc-arm-none-eabi-6-2017-q2-update.tar.bz2
tar -xf gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu.tar.xz
tar -xf gcc-linaro-7.2.1-2017.11-x86_64_aarch64-elf.tar.xz

# 确认解压结果（应有 5 个目录）
ls -d gcc-*/
```

#### 4.5 确认 Dockerfile 包含 COPY 命令

查看 `~/Radxa_CM5/docker/Dockerfile`，确认包含以下 COPY 行：

```dockerfile
COPY ./gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf /opt/gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf
COPY ./gcc-linaro-aarch64-none-elf-4.8-2013.11_linux /opt/gcc-linaro-aarch64-none-elf-4.8-2013.11_linux
COPY ./gcc-linaro-aarch64-none-elf-4.8-2013.11_linux /opt/toolchains/gcc-linaro-aarch64-none-elf-4.8-2013.11_linux
COPY ./gcc-arm-none-eabi-6-2017-q2-update /opt/toolchains/gcc-arm-none-eabi-6-2017-q2-update
COPY ./gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf /opt/toolchains/gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf
COPY ./gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu /opt/toolchains/gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu
COPY ./gcc-linaro-7.2.1-2017.11-x86_64_aarch64-elf /opt/toolchains/gcc-linaro-7.2.1-2017.11-x86_64_aarch64-elf
```

如果 Dockerfile 中没有这些行（备份时可能是旧版本），用以下完整 Dockerfile 替换：

```bash
cat > ~/Radxa_CM5/docker/Dockerfile << 'EOF'
FROM ubuntu:20.04

RUN rm /etc/apt/sources.list
RUN echo "deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse" | tee /etc/apt/sources.list
RUN echo "deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse" >> /etc/apt/sources.list

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update -y && apt-get install -y software-properties-common apt-utils
RUN add-apt-repository -y ppa:deadsnakes/ppa
RUN apt-get update -y && apt-get install -y python3.8
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 150
RUN apt-get install -y python3-pip && pip install pycrypto

RUN apt-get update -y && apt-get install -y openjdk-8-jdk python git-core gnupg flex bison gperf build-essential \
    zip curl gawk liblz4-tool zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \
    libncurses5 libncurses-dev x11proto-core-dev libx11-dev lib32z-dev ccache \
    libgl1-mesa-dev libxml2-utils xsltproc unzip mtools u-boot-tools \
    htop iotop sysstat iftop pigz bc device-tree-compiler lunzip \
    dosfstools vim-common parted udev libssl-dev sudo rsync python3-pyelftools cpio

RUN curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo > /usr/local/bin/repo && \
    chmod +x /usr/local/bin/repo && \
    which repo

ENV REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'

RUN apt-get install -y lzop swig
RUN apt-get update -y && apt-get install -y tzdata

RUN mkdir /opt/toolchains

COPY ./gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf /opt/gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf
COPY ./gcc-linaro-aarch64-none-elf-4.8-2013.11_linux /opt/gcc-linaro-aarch64-none-elf-4.8-2013.11_linux
COPY ./gcc-linaro-aarch64-none-elf-4.8-2013.11_linux /opt/toolchains/gcc-linaro-aarch64-none-elf-4.8-2013.11_linux
COPY ./gcc-arm-none-eabi-6-2017-q2-update /opt/toolchains/gcc-arm-none-eabi-6-2017-q2-update
COPY ./gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf /opt/toolchains/gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf
COPY ./gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu /opt/toolchains/gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu
COPY ./gcc-linaro-7.2.1-2017.11-x86_64_aarch64-elf /opt/toolchains/gcc-linaro-7.2.1-2017.11-x86_64_aarch64-elf

RUN apt-get install -y net-tools gcc-arm-linux-gnueabihf gcc-arm-none-eabi

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
EOF
```

#### 4.6 重建 Docker 镜像

```bash
cd ~/Radxa_CM5/docker
sudo docker build -t android-builder:12.x .
# 耗时约 10-15 分钟
```

#### 4.7 验证环境完整

```bash
sudo docker run --rm android-builder:12.x bash -c \
  'echo "=== 工具链 ===" && ls /opt/toolchains/ && \
   echo "" && echo "=== 编译器 ===" && \
   /opt/gcc-linaro-6.3.1-2017.02-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc --version | head -1 && \
   /opt/toolchains/gcc-linaro-7.2.1-2017.11-x86_64_aarch64-elf/bin/aarch64-elf-gcc --version | head -1 && \
   /opt/toolchains/gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc --version | head -1 && \
   java -version 2>&1 | head -1 && python3 --version && which repo'
```

预期输出：
- /opt/toolchains/ 下有 5 个工具链目录
- arm-linux-gnueabihf-gcc 6.3.1
- aarch64-elf-gcc 7.2.1
- aarch64-linux-gnu-gcc 6.3.1
- openjdk 1.8.0
- Python 3.8.x
- /usr/local/bin/repo

#### 4.8 清理下载的压缩包

```bash
cd ~/Radxa_CM5/docker
rm -f *.tar.xz *.tar.bz2
```

注意：Docker 镜像加速器配置（`/etc/docker/daemon.json`）在备份中已保留，但镜像加速地址可能失效。
如果 `docker build` 拉取 ubuntu:20.04 失败，参考 3.4 节更新加速器地址。

## 5.2 新电脑是 Windows — 快速恢复

### 步骤 1：导入虚拟机

1. 新电脑安装 **VMware Workstation 17**
2. VMware → 文件 → 打开 → 选择 `.ovf` 文件
3. 选择存放位置，等待导入
4. 调整参数（右键 → 设置）：内存 16GB+，CPU 8 核+，网络桥接模式

### 步骤 2：恢复网络和代理

1. 启动虚拟机，检查网络
2. 更新 `/etc/profile.d/proxy.sh` 中的 Windows IP（如果 IP 变化）
3. Windows 安装 Clash，开启 Allow LAN，防火墙放行 7890
4. 安装 Tailscale（Windows + 虚拟机），同账号登录

### 步骤 3：恢复 SDK 并对齐 Dockerfile

1. 拷贝 SDK 压缩包到虚拟机 `~/Radxa_CM5/SDK_Android/`
2. 按上方 **步骤 4（4.1~4.8）** 对齐官方 Dockerfile 环境
3. 解压 SDK 并编译验证

### 步骤 4：配置 Mac 远程开发（如有）

按第四部分重新配置 Mac 端 SSH 和 Tailscale。

## 5.3 新电脑是 Mac（远程控制 Windows 开发）

Mac M 芯片无法运行 x86 虚拟机，采用 Mac 远程控制 Windows 方案。

### 前提条件
- Windows 电脑保持运行（作为编译服务器）
- Windows 上已按 5.2 完成虚拟机恢复和 Dockerfile 对齐
- Mac 和 Windows 都安装 Tailscale

### 步骤 1：Windows 端配置

1. 按 5.2 完成虚拟机导入、网络恢复、SDK 恢复、Dockerfile 对齐

2. 安装 Tailscale（Windows 版），用同一账号登录

3. 安装 Clash for Windows，导入订阅，开启 "允许局域网连接入Clash"
   防火墙放行：
   ```powershell
   netsh advfirewall firewall add rule name="Clash Proxy" dir=in action=allow protocol=tcp localport=7890
   ```

4. 启动虚拟机，确认服务正常：
   ```bash
   tailscale status
   sudo systemctl status ssh
   curl -I https://www.google.com    # 验证外网代理
   docker images android-builder     # 验证 Docker 镜像
   ```

5. （可选）设置虚拟机开机自启：VMware → 编辑 → 首选项 → 共享虚拟机 → 开机启动

### 步骤 2：新 Mac 端配置

1. 安装 Tailscale（App Store），用同一账号登录

2. 安装 ClashX Pro，导入订阅

3. 配置 ClashX Pro 与 Tailscale 共存（按第二部分操作）：
   - 添加 Tailscale 直连规则到代理配置
   - 关闭 Tailscale DNS Settings

4. 生成 SSH 密钥并部署：
   ```bash
   ssh-keygen -t ed25519 -C "new-mac-to-linux-vm"
   ssh-copy-id zhangwenqi@<虚拟机Tailscale_IP>
   ```
   注意：Tailscale IP 可能与之前不同，在虚拟机中执行 `tailscale ip -4` 确认。

5. 配置 SSH config（`~/.ssh/config`）：
   ```
   Host linux-vm
       HostName <虚拟机Tailscale_IP>
       User zhangwenqi
       Port 22
       ServerAliveInterval 30
       TCPKeepAlive yes
       ConnectTimeout 60
   ```

6. 安装 VS Code + Remote-SSH 扩展

7. Cmd+Shift+P → "Remote-SSH: Connect to Host" → linux-vm

### 步骤 3：验证

```bash
# Mac 终端测试
ping <虚拟机Tailscale_IP>
ssh linux-vm

# 验证 Docker 环境完整（应显示 5 套工具链）
ssh linux-vm "docker run --rm android-builder:12.x ls /opt/toolchains/"

# 验证 VPN
# 浏览器访问 YouTube
```

### 日常使用流程

1. Windows 电脑开机 → 虚拟机自动启动（或手动启动）
2. Mac 打开 Tailscale + ClashX Pro
3. VS Code → Remote-SSH → linux-vm
4. 打开 `/home/zhangwenqi/Radxa_CM5` 开始开发

---

# 第六部分：故障排除

## 6.1 Tailscale 连不上

```bash
# Linux 端检查
sudo tailscale status
sudo systemctl restart tailscaled
sudo tailscale up

# 诊断
tailscale netcheck
```

## 6.2 SSH 连接被拒绝

```bash
# Linux 端检查
sudo systemctl status ssh
sudo netstat -tlnp | grep :22
sudo ufw status

# 重启 SSH
sudo systemctl restart ssh
```

## 6.3 VPN 开启 Tailscale 后失效

检查两个配置：
1. ClashX Pro 配置文件中 Tailscale 直连规则是否存在（订阅更新会覆盖）
2. Tailscale DNS Settings 是否已关闭

## 6.4 Docker 拉取镜像超时

Docker Hub 被墙，检查镜像加速配置：
```bash
cat /etc/docker/daemon.json
# 确认 registry-mirrors 配置正确
sudo systemctl restart docker
```

## 6.5 VS Code Remote-SSH 连接超时

在 `~/.ssh/config` 中增大超时：
```
Host linux-vm
    ConnectTimeout 120
    ServerAliveInterval 30
    ServerAliveCountMax 5
```

---

# 第七部分：快速参考

## 日常连接命令

```bash
# Mac 终端直连
ssh linux-vm

# 文件传输
scp file.txt linux-vm:~/
scp -r folder/ linux-vm:~/destination/
```

## VS Code 操作

- Cmd+Shift+P → "Remote-SSH: Connect to Host" → linux-vm
- Cmd+Shift+P → "Remote-SSH: Disconnect"

## Linux 虚拟机管理

```bash
# Tailscale
tailscale ip -4
tailscale status
sudo tailscale down / up

# Docker
docker images
docker run -it --name android-build -v ~/Radxa_CM5/SDK_Android:/workspace -w /workspace android-builder:12.x bash
docker start -ai android-build    # 重新进入已创建的容器

# SSH
sudo systemctl status ssh
sudo systemctl restart ssh
```

---

文档版本: 2.0
最后更新: 2026-04-15
适用系统: Ubuntu 20.04 LTS (x86_64), macOS
网络环境: 跨网络远程开发（Tailscale + ClashX Pro 共存）
