# 22.12 code-server 和 clangd 开发配置

本教程目前在 13.2-RELEASE 和 14.0-RELEASE 上测试正常，其他版本请谨慎参考。

## 概述

code-server 是 Visual Studio Code 的开源服务器版本，可在远程服务器上运行并通过浏览器访问。通过在 FreeBSD 上配置 code-server，无需安装桌面环境即可在原生 FreeBSD 环境中获得集成开发环境，能够利用熟悉的 VSCode 界面和强大的 clangd 插件支持 FreeBSD 内核开发，从而降低代码贡献与二次开发的学习成本。

由于上游 FreeBSD 版 code-server 存在问题，历史版本中仅有一个修订版可用。此外，自 code-server 基于 Node.js 18 以来，对 glibc 的最低版本要求提高，CentOS 提供的运行时环境已无法满足其需求。因此通过 Linux 兼容层运行 code-server 是目前最省时省力的方案。

本教程使用 Arch Linux bootstrap 镜像构建 Linux 兼容层环境。选择 Arch Linux 的原因是其滚动更新特性能够提供较新版本的 glibc 和其他依赖库，满足 code-server 的运行要求。

## Linux 兼容层路径映射机制

虽然为了运行 code-server 使用了 Linux 兼容层，但 clangd 以及其他所有开发工具仍然完全由 FreeBSD 提供。这一机制基于 FreeBSD 内核的路径劫持特性。

首先需要明确，Linux 二进制兼容模式不是 Linux 模拟器，也不是 Linux 虚拟机。运行 Linux 程序的主体仍然是 FreeBSD 内核本身。

由于主体仍是 FreeBSD 内核，这必然涉及 Linux 程序和 FreeBSD 程序的混合运行，而混合运行又会带来动态链接库调用的问题。对于一个已经编译好的二进制程序来说，它所需的动态链接库路径是写死的，比如如果某个 Linux 二进制程序依赖 `/lib/glibc.so`，那么它一定会去 `/lib/glibc.so` 查找这个文件，而非其他地方。但在 FreeBSD 下，Linux 的运行时环境位于 `/compat/linux` 目录下。

为了解决这个问题，FreeBSD 在内核层面劫持路径：当一个 Linux 二进制程序尝试 open `/lib/glibc.so` 时，FreeBSD 内核会自动在路径前加上 `/compat/linux`，使其变为 `/compat/linux/lib/glibc.so`。这一过程对应用程序而言是透明的，应用程序自身并不知道它最终获得的是哪个文件，但从 FreeBSD 内核的视角来看，它实际访问的是 `/compat/linux/lib/glibc.so`。

如果某个文件不在 `/compat/linux` 目录下，而是在 FreeBSD 本地系统目录中，FreeBSD 内核会自动回退到原始路径进行二次尝试。如果仍然找不到文件，open 系统调用才会真正失败。

回到 code-server，它作为一款 Linux 程序，在尝试访问文件或目录时，默认会先在 `/compat/linux` 下查找。假设想打开 `/usr/src` 目录以查看 FreeBSD 的源代码树，如果 `/compat/linux/usr/src` 存在，那么实际被打开的是 `/compat/linux/usr/src`，而不是真正需要的 `/usr/src`。因此需要删除 `/compat/linux/usr/src`，确保 FreeBSD 内核能够 fallback 到正确的 `/usr/src`。

同样的逻辑也适用于 clangd。由于 `/compat/linux/bin/clangd` 并不存在，当 code-server 试图启动 clangd 时，它最终会调用 FreeBSD 提供的 `/usr/local/bin/clangd`，其他开发工具也同理。

## 服务器启用 Linux 二进制兼容，并部署 archlinux-bootstrap 镜像

首先启用 Linux 兼容层服务并设置开机自启：

```sh
# service linux enable   # 设置 Linux 兼容层服务开机自启
# service linux start    # 启动 Linux 兼容层服务
```

下载并解压 Arch Linux 的 bootstrap 镜像：

```sh
# fetch -o /tmp https://mirrors.bfsu.edu.cn/archlinux/iso/latest/archlinux-bootstrap-x86_64.tar.zst   # 下载 Arch Linux bootstrap 镜像到 /tmp
# tar --use-compress-program=unzstd -xpvf /tmp/archlinux-bootstrap-x86_64.tar.zst -C /tmp --numeric-owner --strip-components=1	# 解压 Arch Linux bootstrap 镜像到 FreeBSD Linux 兼容目录
# rm /tmp/archlinux-bootstrap-x86_64.tar.zst   # 删除下载的压缩包
```

## 服务器配置 pacman 源，并添加 archlinuxcn 仓库

软件源文件结构：

```sh
/compat/
└── linux/
    └── etc/
        └── pacman.conf # Pacman 包管理器配置文件
```

编辑 `/compat/linux/etc/pacman.conf`，配置软件源：

```sh
[options]
Architecture = auto
ParallelDownloads = 5

[core]
Server = https://mirrors.cernet.edu.cn/archlinux/\$repo/os/\$arch
SigLevel = Required DatabaseOptional

[extra]
Server = https://mirrors.cernet.edu.cn/archlinux/\$repo/os/\$arch
SigLevel = Required DatabaseOptional

[archlinuxcn]
Server = https://mirrors.cernet.edu.cn/archlinuxcn/\$arch
SigLevel = Required DatabaseOptional
```

## 服务器初始化 Arch Linux 运行时环境

在 Linux 兼容环境中初始化 pacman 密钥环：

```sh
# chroot /compat/linux pacman-key --init       # 在 Linux 兼容环境中初始化 pacman 密钥环
# chroot /compat/linux pacman-key --populate   # 在 Linux 兼容环境中填充 pacman 默认密钥
```

## 服务器更新 Arch Linux 运行时环境，并安装 code-server

DNS 解析相关文件结构：

```sh
/
├── etc
│   └── resolv.conf                 # DNS 解析配置文件
└── compat
    └── linux
        └── etc
            └── resolv.conf         # Linux 兼容层 DNS 配置文件
```

复制 FreeBSD 的 DNS 配置到 Linux 兼容环境：

```sh
# cp /etc/resolv.conf /compat/linux/etc
```

在 Linux 兼容环境中更新软件包并安装 code-server：

```sh
# chroot /compat/linux pacman -Syu --noconfirm               # 在 Linux 兼容环境中更新所有软件包，无需确认
# chroot /compat/linux pacman -S --noconfirm archlinuxcn-keyring   # 安装 archlinuxcn-keyring 软件包，无需确认
# chroot /compat/linux pacman -S --noconfirm code-server           # 安装 code-server 软件包，无需确认
```

## 服务器删除 Arch Linux 运行时环境中的无用目录

删除 Linux 兼容环境中可能干扰 FreeBSD 本地路径访问的目录：

```sh
# rm -Rf /compat/linux/home       # 删除 Linux 兼容层中的 /home 目录及其内容
# rm -Rf /compat/linux/root       # 删除 Linux 兼容层中的 /root 目录及其内容
# rm -Rf /compat/linux/usr/local  # 删除 Linux 兼容层中的 /usr/local 目录及其内容
# rm -Rf /compat/linux/usr/src    # 删除 Linux 兼容层中的 /usr/src 目录及其内容
```

现有目录状态：

```sh
/compat/
└── linux/
    ├── home/ # 用户主目录（已删除）
    ├── root/ # root 用户主目录（已删除）
    └── usr/
        ├── local/ # 本地软件目录（已删除）
        └── src/ # 源代码目录（已删除）
```

## 服务器安装 LLVM 与 clangd 插件

在 FreeBSD 本地安装 LLVM 工具链，并配置 code-server：

```sh
# pkg install -y llvm   # 安装 LLVM 工具链
# ln -sf /compat/linux/lib/code-server/bin/code-server /usr/local/bin   # 创建 code-server 的软链接到 /usr/local/bin
# code-server --install-extension llvm-vs-code-extensions.vscode-clangd   # 在 code-server 中安装 clangd 扩展
```

目录结构：

```sh
/
├── compat
│   └── linux
│       └── lib
│           └── code-server
│               └── bin
│                   └── code-server           # code-server 可执行文件
├── usr
│   └── local
│       └── bin
│           └── code-server                   # code-server 软链接
└── root
    └── .code-server.pid                       # code-server PID 文件
```

## 服务器通过 daemon 命令启动 code-server

使用 daemon 工具后台启动 code-server，关闭认证并将 PID 写入指定文件：

```sh
# daemon -p /root/.code-server.pid -f code-server --auth=none
```

> **注意**
>
> 使用 root 权限操作时请注意风险，做好数据备份。

## 客户端通过 SSH 建立隧道并通过浏览器连接到 code-server 服务器

在客户端建立 SSH 本地端口转发，将本地 8080 端口映射到远程服务器的 127.0.0.1:8080：

```sh
# ssh -L 8080:127.0.0.1:8080 -N root@server  # -N 表示不执行远程命令，仅负责转发网络流量
```

在浏览器中访问 <http://127.0.0.1:8080>

## （示例）浏览器中用 code-server 打开 FreeBSD 的源代码树

使用 code-server 打开 `/usr/src` 目录作为工作区：

```sh
# code-server /usr/src
```

## （示例）在浏览器中编译最小化内核并生成 `compile_commands.json` 文件

安装 Bear 工具并生成内核构建的编译命令数据库：

```sh
# pkg install bear   # 安装 Bear 工具，用于生成 compile_commands.json
# bear --append -- make KERNCONF=MINIMAL buildkernel   # 使用 Bear 生成内核构建的编译命令，并构建 MINIMAL 内核
```

等待编译完成并生成 `compile_commands.json` 文件后，即可开始阅读内核关键部分的源代码。

## 自动化安装脚本

为快速获得开发环境，以下是 code-server 安装步骤的自动化脚本：

> **警告**
>
> 此脚本会破坏现有的任何 Linux 兼容层。读者应在理解的基础上再执行，并做好备份工作。

```sh
#!/bin/sh

set -e

ARCHLINUX_MIRROR="https://mirrors.cernet.edu.cn/archlinux"
ARCHLINUXCN_MIRROR="https://mirrors.cernet.edu.cn/archlinuxcn"
FREEBSD_PKG_MIRROR="https://mirrors.cernet.edu.cn/FreeBSD-pkg"

rm -Rf /compat/linux
rm -Rf /tmp/archlinux-bootstrap-x86_64.tar.zst

service linux enable
service linux start

fetch -o /tmp "$ARCHLINUX_MIRROR/iso/latest/archlinux-bootstrap-x86_64.tar.zst"
tar --use-compress-program=unzstd -xpvf /tmp/archlinux-bootstrap-x86_64.tar.zst -C /tmp --numeric-owner --strip-components=1

cat >/compat/linux/etc/pacman.conf <<EOF
[options]
Architecture = auto
ParallelDownloads = 5

[core]
Server = $ARCHLINUX_MIRROR/\$repo/os/\$arch
SigLevel = Required DatabaseOptional

[extra]
Server = $ARCHLINUX_MIRROR/\$repo/os/\$arch
SigLevel = Required DatabaseOptional

[archlinuxcn]
Server = $ARCHLINUXCN_MIRROR/\$repo/os/\$arch
SigLevel = Required DatabaseOptional
EOF

chroot /compat/linux pacman-key --init
chroot /compat/linux pacman-key --populate

cp /etc/resolv.conf /compat/linux/etc

chroot /compat/linux pacman --sync --refresh --sysupgrade --noconfirm
chroot /compat/linux pacman --sync --needed --noconfirm archlinuxcn-keyring
chroot /compat/linux pacman --sync --needed --noconfirm code-server

ln -sf /compat/linux/lib/code-server/bin/code-server /usr/local/bin

rm -Rf /compat/linux/home
rm -Rf /compat/linux/root
rm -Rf /compat/linux/usr/local
rm -Rf /compat/linux/usr/src

pkg upgrade -y git bash vim htop tmux llvm bear

code-server --install-extension llvm-vs-code-extensions.vscode-clangd
code-server --install-extension mhutchie.git-graph

rm -Rf /tmp/archlinux-bootstrap-x86_64.tar.zst
```

目录结构：

```sh
/
├── compat
│   └── linux
│       ├── etc
│       │   ├── pacman.conf             # Arch Linux pacman 配置文件
│       │   └── resolv.conf             # DNS 解析配置（从 FreeBSD 复制）
│       ├── lib
│       │   └── code-server
│       │       └── bin
│       │           └── code-server     # code-server 可执行文件
│       └── usr
│           ├── local                     # （已删除）
│           └── src                       # （已删除）
├── usr
│   ├── local
│   │   └── bin
│   │       ├── code-server             # code-server 软链接（指向 /compat/linux/lib/code-server/bin/code-server）
│   │       └── clangd                   # clangd 可执行文件
│   └── src                              # FreeBSD 源代码目录（用于 code-server 工作区）
└── root
    └── .code-server.pid                 # code-server 进程 ID 文件
```

欢迎测试并反馈。

## 补充说明

### 关于 HTTPS 配置

本教程未涉及如何在服务器上通过 HTTPS 提供 code-server 服务。如需配置 HTTPS，可参考 code-server 官方文档进行 SSL 证书配置。

### Linux 兼容层与 Linux Jail

本教程使用 Linux 二进制兼容层，而非 Linux Jail。两者的主要区别在于：Linux 兼容层共享 FreeBSD 内核，而 Linux Jail 提供更隔离的运行环境。

## 课后习题

1. 使用自动化脚本部署 code-server 环境，通过 SSH 隧道连接，打开 FreeBSD 源代码树并使用 clangd 阅读内核代码。
2. 修改自动化脚本，配置 HTTPS 认证并测试远程访问安全性。
3. 对比 Linux 兼容层与原生 FreeBSD 方案在运行 code-server 上的实现差异，探讨为何兼容层是目前的最优解。
