# 26.2.更新 FreeBSD

及时应用安全补丁和升级操作系统到较新版本是持续系统管理的重要方面。FreeBSD 提供了一个名为 `freebsd-update` 的工具，可以用来执行这两个任务。

该工具支持对 FreeBSD 的二进制安全更新和修复更新，无需手动编译和安装补丁或新的内核。二进制更新适用于当前安全团队支持的所有架构和版本。支持的版本及其估计的生命周期结束日期可以在 [https://www.FreeBSD.org/security/](https://www.freebsd.org/security/) 查找。

该工具还支持操作系统的升级，包括升级到次要点版本以及升级到另一个发布分支。在升级到新版本之前，查看其发布公告非常重要，因为公告中包含了与该版本相关的重要信息。发布公告可以在 [https://www.FreeBSD.org/releases/](https://www.freebsd.org/releases/) 找到。

> **注意**
>
> 如果存在使用 [freebsd-update(8)](https://man.freebsd.org/cgi/man.cgi?query=freebsd-update\&sektion=8\&format=html) 功能的 [crontab(5)](https://man.freebsd.org/cgi/man.cgi?query=crontab\&sektion=5\&format=html)，则在升级操作系统之前，必须先禁用该任务。

本节介绍了 `freebsd-update` 使用的配置文件，演示了如何应用安全补丁以及如何升级到次要或主要的操作系统版本，并讨论了升级操作系统时的一些注意事项。

## 26.2.1. 配置文件

`freebsd-update` 的默认配置文件可直接使用。一些用户可能希望在 **/etc/freebsd-update.conf** 中调整默认配置，以更好地控制过程。文件中的注释解释了可用选项，但以下内容可能需要进一步解释：

```sh
# 应保持更新的基本系统组件。
Components world kernel
```

该参数控制 FreeBSD 将更新哪些部分。默认情况下，它会更新整个基本系统和内核。可以指定单独的组件，如 `src/base` 或 `src/sys`。然而，最佳选择是保持默认配置，因为将其更改为包括特定项需要列出每个需要的项。随着时间推移，这可能会导致源代码和二进制文件不同步，从而产生灾难性后果。

```sh
# 以忽略路径中匹配 IgnorePaths 中的条目的路径。
IgnorePaths /boot/kernel/linker.hints
```

要在更新过程中保持某些目录（如 **/bin** 或 **/sbin**）不受影响，请将其路径添加到此语句中。此选项可用于防止 `freebsd-update` 覆盖本地修改。

```sh
# 以忽略路径中匹配 UpdateIfUnmodified 中的条目的路径。
# 仅当文件内容未被用户修改时，才更新这些文件（除非有合并操作；见下文）。
UpdateIfUnmodified /etc/ /var/ /root/ /.cshrc /.profile
```

此选项将仅更新未修改的配置文件。用户做出的任何更改将阻止这些文件的自动更新。还有一个选项，`KeepModifiedMetadata`，它将指示 `freebsd-update` 在合并时保存更改。

```sh
# 升级到新 FreeBSD 版本时，匹配 MergeChanges 的文件将会合并用户的本地更改。
MergeChanges /etc/ /var/named/etc/ /boot/device.hints
```

列出需要合并的配置文件目录。文件合并过程是一个 [diff(1)](https://man.freebsd.org/cgi/man.cgi?query=diff\&sektion=1\&format=html) 补丁。合并可能会被接受、打开编辑器或导致 `freebsd-update` 中止。若有疑问，备份 **/etc** 并接受合并。

```sh
# 存储下载的更新和 FreeBSD Update 使用的临时文件的目录。
# WorkDir /var/db/freebsd-update
```

此目录用于存储所有补丁和临时文件。在用户进行版本升级时，应该确保该位置有至少 1GB 的可用磁盘空间。

```sh
# 升级版本时，是否严格读取组件列表（StrictComponents yes），
# 或者只是作为 FreeBSD Update 应该自动识别哪些已安装并升级的组件的列表（StrictComponents no）？
# StrictComponents no
```

当此选项设置为 `yes` 时，`freebsd-update` 将假定 `Components` 列表是完整的，并且不会尝试更改列表之外的内容。实际上，`freebsd-update` 将尝试更新每个属于 `Components` 列表的文件。

有关更多详细信息，请参考 [freebsd-update.conf(5)](https://man.freebsd.org/cgi/man.cgi?query=freebsd-update.conf\&sektion=5\&format=html)。

## 26.2.2. 应用安全补丁

应用 FreeBSD 安全补丁的过程已经简化，使管理员可以使用 `freebsd-update` 保持系统的完全补丁更新。有关 FreeBSD 安全公告的更多信息，请访问 [FreeBSD 安全公告](https://docs.freebsd.org/en/books/handbook/security/#security-advisories)。

可以使用以下命令下载并安装 FreeBSD 安全补丁。第一个命令将确定是否有任何未应用的补丁，如果有，它将列出将被修改的文件。第二个命令将应用补丁。

```sh
# freebsd-update fetch
# freebsd-update install
```

如果更新应用了任何内核补丁，系统需要重启才能启动到修补后的内核。如果补丁应用于任何正在运行的二进制文件，应重新启动相关的应用程序，以确保使用修补后的版本。

> **注意**
>
> 通常，用户需要准备重启系统。要知道系统是否需要重启，可以执行命令 `freebsd-version -k` 和 `uname -r`。如果输出不同，则需要重启系统。

系统可以配置为每天自动检查更新，在 **/etc/crontab** 中添加以下条目：

```sh
@daily                                  root    freebsd-update cron
```

如果存在补丁，它们将被自动下载，但不会应用。`root` 用户将收到一封电子邮件，以便审查补丁并手动使用 `freebsd-update install` 安装它们。

如果发生任何问题，`freebsd-update` 具有回滚最后一组更改的能力，使用以下命令：

```sh
# freebsd-update rollback
Uninstalling updates... done.
```

同样，如果内核或任何内核模块被修改，系统应重新启动，任何受影响的二进制文件应重新启动。

只有 **GENERIC** 内核可以由 `freebsd-update` 自动更新。如果安装了自定义内核，`freebsd-update` 完成更新后，必须重新构建并重新安装该内核。默认内核名是 *GENERIC*。可以使用 [uname(1)](https://man.freebsd.org/cgi/man.cgi?query=uname\&sektion=1\&format=html) 命令来验证其安装。

> **注意**
>
> 请始终在 **/boot/GENERIC** 中保留 **GENERIC** 内核的副本。这对于诊断各种问题和执行版本升级非常有帮助。有关如何获取 **GENERIC** 内核副本的说明，请参见 [FreeBSD 9.X 及以后的自定义内核](https://docs.freebsd.org/en/books/handbook/cutting-edge/#freebsd-update-custom-kernel-9x)。

除非在 **/etc/freebsd-update.conf** 中更改了默认配置，否则 `freebsd-update` 会与其他更新一起安装更新的内核源代码。然后，可以按通常的方式重新构建并重新安装新的自定义内核。

`freebsd-update` 分发的更新并不总是涉及内核。如果内核源代码没有被 `freebsd-update install` 修改，则不需要重新构建自定义内核。但是，`freebsd-update` 会始终更新 **/usr/src/sys/conf/newvers.sh** 文件。当前的补丁级别（由 `uname -r` 报告的 `-p` 数字）来自该文件。即使没有其他更改，重新构建自定义内核也会使 `uname` 准确报告系统当前的补丁级别。这在维护多个系统时尤其有用，因为它允许快速评估每个系统安装的更新情况。

## 26.2.3. 执行小版本和大版本升级

从 FreeBSD 的一个小版本升级到另一个小版本称为 *小版本* 升级。例如：

* 从 FreeBSD 13.1 升级到 13.2。
* *大版本* 升级会增加主版本号。例如：
* 从 FreeBSD 13.2 升级到 14.0。

这两种类型的升级都可以通过为 `freebsd-update` 提供目标版本来进行。

> **警告**
>
> 每次新的 `RELEASE` 发布后，FreeBSD 包构建服务器会在有限的时间内 **不** 使用操作系统的较新版本。这为许多不立即进行升级的用户提供了连续性。
>
> 例如：
>
> * 13.1 和 13.2 用户的包将会在运行 13.1 的服务器上构建，直到 13.1 达到生命周期的终点；
>
> 并且，**关键**：
>
> * 在 13.1 上构建的内核模块 **可能** 不适用于 13.2。
>
> 所以，在进行任何小版本或大版本升级时，如果你的包要求包含内核模块，
>
> * **准备从源代码构建模块**。

> **注意**
>
> 如果系统运行的是自定义内核，请确保在开始升级之前，**/boot/GENERIC** 中有一份 **GENERIC** 内核副本。有关如何获取 **GENERIC** 内核的说明，请参阅 [FreeBSD 9.X 及以后的自定义内核](https://docs.freebsd.org/en/books/handbook/cutting-edge/#freebsd-update-custom-kernel-9x)。

在升级到新版本之前，确保现有的 FreeBSD 安装已针对安全和补丁更新进行更新：

```sh
# freebsd-update fetch
# freebsd-update install
```

以下命令在 FreeBSD 13.1 系统上执行时，将其升级到 FreeBSD 13.2：

```sh
# freebsd-update -r 13.2-RELEASE upgrade
```

收到命令后，`freebsd-update` 将评估配置文件和当前系统，以便收集执行升级所需的信息。此时会显示一屏列出已安装和未安装的组件。例如：

```sh
Looking up update.FreeBSD.org mirrors... 1 mirrors found.
Fetching metadata signature for 13.1-RELEASE from update1.FreeBSD.org... done.
Fetching metadata index... done.
Inspecting system... done.

The following components of FreeBSD seem to be installed:
kernel/smp src/base src/bin src/contrib src/crypto src/etc src/games
src/gnu src/include src/krb5 src/lib src/libexec src/release src/rescue
src/sbin src/secure src/share src/sys src/tools src/ubin src/usbin
world/base world/info world/lib32 world/manpages

The following components of FreeBSD do not seem to be installed:
kernel/generic world/catpages world/dict world/doc world/games
world/proflibs

Does this look reasonable (y/n)? y
```

此时，`freebsd-update` 将尝试下载升级所需的所有文件。在某些情况下，用户可能会被提示选择安装内容或如何继续。

如果使用的是自定义内核，则上述步骤将产生类似以下的警告：

```sh
WARNING: This system is running a "MYKERNEL" kernel, which is not a
kernel configuration distributed as part of FreeBSD 13.1-RELEASE.
This kernel will not be updated: you MUST update the kernel manually
before running "/usr/sbin/freebsd-update install"
```

此警告此时可以安全忽略。更新后的 **GENERIC** 内核将作为升级过程中的中间步骤使用。

待所有补丁被下载到本地系统，它们将被应用。此过程可能会根据机器的速度和工作负载持续一段时间。配置文件将随后进行合并。合并过程需要用户干预，因为可能会合并某个文件，或者屏幕上会出现编辑器要求手动合并。每次成功合并的结果将继续显示给用户。如果某个合并失败或被忽略，过程将会中止。用户可能希望先备份 **/etc**，然后在稍后手动合并重要文件，如 **master.passwd** 或 **group**。

> **注意**
>
> 系统尚未发生任何更改，因为所有的补丁和合并操作都发生在另一个目录中。待所有补丁成功应用，所有配置文件合并完成，并且过程似乎顺利进行，用户可以使用以下命令将更改提交到磁盘：
>
> ```sh
> # freebsd-update install
> ```

首先会更新内核和内核模块。如果系统运行的是自定义内核，请使用 [nextboot(8)](https://man.freebsd.org/cgi/man.cgi?query=nextboot\&sektion=8\&format=html) 将下次启动的内核设置为更新后的 **/boot/GENERIC**：

```sh
# nextboot -k GENERIC
```

> **警告**
>
> 在使用 **GENERIC** 内核重新启动之前，请确保它包含所有启动系统所需的驱动程序，并能够连接到网络，特别是如果正在远程访问被更新的机器。特别是，如果正在运行的自定义内核包含通常由内核模块提供的内置功能，请确保暂时将这些模块加载到 **GENERIC** 内核中，方法是使用 **/boot/loader.conf**。建议在升级过程完成之前禁用非必要的服务以及所有磁盘和网络挂载。

机器现在应该使用更新后的内核重启：

```sh
# shutdown -r now
```

系统重新启动后，使用以下命令重新启动 `freebsd-update`。由于过程状态已被保存，`freebsd-update` 将不会从头开始，而是继续进入下一个阶段，删除所有旧的共享库和目标文件。

```sh
# freebsd-update install
```

> **注意**
>
> 根据是否有库版本号被提升，可能会只有两个安装阶段，而不是三个。

升级现已完成。如果这是一次大版本升级，请按照 [大版本升级后重新安装包和 Port](https://docs.freebsd.org/en/books/handbook/cutting-edge/#freebsdupdate-portsrebuild) 中的说明重新安装所有 Port 和包。

### 26.2.3.1. FreeBSD 9.X 及以后的自定义内核

在使用 `freebsd-update` 之前，确保 **/boot/GENERIC** 中存在一份 **GENERIC** 内核。如果只构建过一次自定义内核，则 **/boot/kernel.old** 中的内核即为 `GENERIC` 内核。只需将该目录重命名为 **/boot/GENERIC** 即可。

如果已构建了多次自定义内核，或者不知道自定义内核构建了多少次，则需要获取与当前操作系统版本匹配的 `GENERIC` 内核副本。如果可以物理访问系统，可以从安装介质安装 `GENERIC` 内核：

```sh
# mount /cdrom
# cd /cdrom/usr/freebsd-dist
# tar -C/ -xvf kernel.txz boot/kernel/kernel
```

或者，可以从源代码重建并安装 `GENERIC` 内核：

```sh
# cd /usr/src
# make kernel __MAKE_CONF=/dev/null SRCCONF=/dev/null
```

为了让 `freebsd-update` 将此内核识别为 `GENERIC` 内核，**GENERIC** 配置文件必须没有做任何修改。同时建议在没有其他特殊选项的情况下构建该内核。

不需要重新启动到 **GENERIC** 内核，因为 `freebsd-update` 只需要 **/boot/GENERIC** 存在即可。

### 26.2.3.2. 大版本升级后的软件包升级

通常，在小版本升级后，已安装的应用程序会继续正常工作。大版本使用不同的应用程序二进制接口（ABI），这会导致大多数第三方应用程序出现问题。大版本升级后，需要升级所有已安装的包和 Port。可以使用 `pkg upgrade` 升级包。要升级已安装的 Port，可以使用如 [ports-mgmt/portmaster](https://cgit.freebsd.org/ports/tree/ports-mgmt/portmaster/) 这样的工具。

强制升级所有已安装的包将会从仓库中获取新版本，即使版本号没有增加。由于在 FreeBSD 的大版本升级过程中 ABI 版本发生了变化，强制升级是必要的。可以通过执行以下命令来强制升级：

```sh
# pkg-static upgrade -f
```

可以通过以下命令重建所有从 Ports 安装的应用程序：

```sh
# portmaster -af
```

该命令将为每个具有可配置选项的应用程序显示配置屏幕，并等待用户与这些屏幕进行交互。为防止这种行为并仅使用默认选项，可以在上述命令中加上 `-G` 参数。

待软件升级完成，使用最终的 `freebsd-update` 调用来完成升级过程：

```sh
# freebsd-update install
```

如果临时使用了 **GENERIC** 内核，这是构建并安装新自定义内核的时机，具体步骤请参见 [配置 FreeBSD 内核](https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig)。

重启机器进入新的 FreeBSD 版本。升级过程至此完成。

## 26.2.4. 系统状态比较

可以使用 `freebsd-update IDS` 命令将已安装的 FreeBSD 版本与已知的良好副本进行比较。此命令评估当前版本的系统实用程序、库和配置文件，并可以作为内置的入侵检测系统（IDS）使用。

> **警告**
>
> 此命令并不能替代真实的 IDS，例如 [security/snort](https://cgit.freebsd.org/ports/tree/security/snort/)。由于 `freebsd-update` 会在磁盘上存储数据，因此篡改的可能性是显而易见的。虽然可以通过使用 `kern.securelevel` 和在不使用时将 `freebsd-update` 数据存储在只读文件系统上来减少这种可能性，但更好的解决方案是将系统与安全的磁盘进行比较，例如 DVD 或安全存储的外部 USB 磁盘设备。另一种使用内置工具提供 IDS 功能的方法请参见 [二进制验证](https://docs.freebsd.org/en/books/handbook/security/#security-ids)。

要开始比较，指定输出文件来保存结果：

```sh
# freebsd-update IDS >> outfile.ids
```

系统将会被检查，输出包括一个很长的文件列表以及与发布版本和当前安装版本的 SHA256 哈希值，这些信息会被发送到指定的输出文件。

输出中的条目非常长，但其格式容易解析。例如，要获得与发布版本不同的所有文件的列表，可以执行以下命令：

```sh
# cat outfile.ids | awk '{ print $1 }' | more
/etc/master.passwd
/etc/motd
/etc/passwd
/etc/pf.conf
```

此示例输出已被截断，因为存在更多的文件。一些文件可能自然发生修改。例如，如果已向系统添加用户，**/etc/passwd** 会被修改。内核模块可能会有所不同，因为 `freebsd-update` 可能已经更新了它们。要排除特定的文件或目录，可以将它们添加到 **/etc/freebsd-update.conf** 中的 `IDSIgnorePaths` 选项中。
