# 21.6 openSUSE 兼容层

本节将系统地介绍在 FreeBSD 操作系统上构建 openSUSE Linux 兼容层的技术方法与实践步骤。

## 基于 openSUSE Leap dnf 容器的 shell 脚本

本章节所提供的自动化脚本采用 openSUSE Leap dnf 容器镜像作为基础系统。值得注意的是，尽管该镜像基于 dnf 包管理器构建，但 openSUSE 中的 opi 工具会自动安装 zypper 包管理器，从而实现双包管理器的共存机制。不过，用户仍需按照 [清华大学镜像站 openSUSE 使用帮助](https://mirrors.tuna.tsinghua.edu.cn/help/opensuse/) 的指引，手动完成 zypper 软件源的配置工作。

脚本内容如下：

```sh
#!/bin/sh                                              # 指定脚本使用的 shell

rootdir=/compat/suse                                   # 定义 openSUSE 兼容层根目录
url="https://download.opensuse.org/pub/opensuse/distribution/leap/15.4/appliances/opensuse-leap-dnf-image.x86_64-lxc-dnf.tar.xz"  # openSUSE bootstrap 下载地址

echo "Starting openSUSE Leap 15.4 installation..."       # 输出安装开始信息
echo "check modules ..."                                # 输出模块检查信息

# check linux module
if [ "$(sysrc -n linux_enable)" = "NO" ]; then         # 检查 Linux 内核模块是否启用
        echo "linux module should be loaded. Continue?(Y|n)"  # 提示是否继续
        read answer                                     # 读取用户输入
        case $answer in
                [Nn][Oo]|[Nn])
                        echo "linux module not loaded" # 提示模块未加载
                        exit 1                         # 退出脚本
                        ;;
                [Yy][Ee][Ss]|[Yy]|"")
                        sysrc linux_enable=YES         # 启用 Linux 内核模块
                        ;;
        esac
fi
echo "start linux"                                       # 输出启动 Linux 信息
service linux start                                      # 启动 Linux 内核模块

# check dbus
if ! /usr/bin/which -s dbus-daemon;then                # 检查 dbus-daemon 是否存在
        echo "dbus-daemon not found. install it [Y|n]" # 提示安装 dbus
        read  answer                                    # 读取用户输入
        case $answer in
            [Nn][Oo]|[Nn])
                echo "dbus not installed"               # 提示未安装 dbus
                exit 2                                  # 退出脚本
                ;;
            [Yy][Ee][Ss]|[Yy]|"")
                pkg install -y dbus                     # 安装 dbus
                ;;
        esac
    fi

if [ "$(sysrc -n dbus_enable)" != "YES" ]; then        # 检查 dbus 服务是否启用
        echo "dbus should be enabled. Continue?(Y|n)"  # 提示是否启用
        read answer                                     # 读取用户输入
        case $answer in
            [Nn][Oo]|[Nn])
                        echo "dbus not running"       # 提示 dbus 未运行
                        exit 2                          # 退出脚本
                        ;;
            [Yy][Ee][Ss]|[Yy]|"")
                        service dbus enable            # 启用 dbus 服务开机自启
                        ;;
        esac
fi
echo "start dbus"                                       # 输出启动 dbus 信息
service dbus start                                      # 启动 dbus 服务

echo "now we will bootstrap openSUSE"                  # 输出 bootstrap 开始信息

fetch ${url}                                            # 下载 openSUSE bootstrap 压缩包
mkdir -p ${rootdir}                                    # 创建目标目录
tar xvf opensuse-leap-dnf-image*.tar.xz -C ${rootdir} --numeric-owner  # 解压压缩包
rm opensuse-leap-dnf-image.x86_64-lxc-dnf.tar.xz       # 删除压缩包

if [ ! "$(sysrc -f /boot/loader.conf -qn nullfs_load)" = "YES" ]; then  # 检查 nullfs 是否加载
        echo "nullfs_load should load. continue? (Y|n)"  # 提示是否继续
        read answer                                     # 读取用户输入
        case $answer in
            [Nn][Oo]|[Nn])
                echo "nullfs is not loaded"                  # 提示未加载
		exit 3                                      # 退出脚本
                ;;
            [Yy][Ee][Ss]|[Yy]|"")
                sysrc -f /boot/loader.conf nullfs_load=yes  # 设置 nullfs 开机自启
                ;;
        esac
fi

if ! kldstat -n nullfs >/dev/null 2>&1;then            # 检查 nullfs 模块是否已加载
        echo "load nullfs module"                      # 输出加载信息
        kldload -v nullfs                               # 加载 nullfs 模块
fi

echo "mount some fs for linux"                         # 输出挂载文件系统信息
echo "devfs ${rootdir}/dev devfs rw,late 0 0" >> /etc/fstab        # devfs 挂载
echo "tmpfs ${rootdir}/dev/shm tmpfs rw,late,size=1g,mode=1777 0 0" >> /etc/fstab  # tmpfs 挂载
echo "fdescfs ${rootdir}/dev/fd fdescfs rw,late,linrdlnk 0 0" >> /etc/fstab        # fdescfs 挂载
echo "linprocfs ${rootdir}/proc linprocfs rw,late 0 0" >> /etc/fstab               # linprocfs 挂载
echo "linsysfs ${rootdir}/sys linsysfs rw,late 0 0" >> /etc/fstab                  # linsysfs 挂载
echo "/tmp ${rootdir}/tmp nullfs rw,late 0 0" >> /etc/fstab                         # /tmp 挂载
#echo "/home ${rootdir}/home nullfs rw,late 0 0" >> /etc/fstab                     # /home 挂载，可选
mount -al                                               # 挂载 fstab 中所有文件系统

echo "For openSUSE, we should change 'compat.linux.osrelease'. Continue? (Y|n)"  # 提示修改 Linux 内核版本
read answer                                             # 读取用户输入
case $answer in
	[Nn][Oo]|[Nn])
		echo "close to success"                        # 提示跳过修改
		exit 4
		;;
	[Yy][Ee][Ss]|[Yy]|"")
		echo "compat.linux.osrelease=6.2.10" >> /etc/sysctl.conf  # 持久化设置 Linux 内核版本
		sysctl compat.linux.osrelease=6.2.10                     # 立即生效
                ;;
esac
echo "complete!"                                        # 输出完成信息
echo "to use: chroot ${rootdir} /bin/bash"             # 提示使用 chroot 进入 openSUSE 兼容层
echo ""
echo " I will set resolv.conf to ali dns"              # 提示将 DNS 设置为阿里 DNS
echo "continue?[Y|n]"                                   # 提示是否继续
read answer                                             # 读取用户输入
case $answer in
	[Nn][Oo]|[Nn])
		echo "set your openSUSE by yourself. Bye!"  # 提示用户自行配置
		exit 0
		;;
	[Yy][Ee][Ss]|[Yy]|"")
		echo "nameserver 223.5.5.5" >> ${rootdir}/etc/resolv.conf   # 设置 DNS

    echo " I will install opi nano tar and vim"           # 输出安装软件信息
    chroot ${rootdir} /bin/bash -c "dnf update && dnf install -y  opi nano tar vim"  # 安装常用软件
    
    echo "all done."                                      # 输出完成信息
    echo "Now you can run '#chroot /compat/suse/ /bin/bash' to enter openSUSE "  # 提示用户进入 openSUSE
		
                ;;
esac
```

## openSUSE Tumbleweed-shell 脚本

本节有点问题，暂不可用。

Tumbleweed 即风滚草，是 openSUSE 的滚动更新版本。

脚本内容如下：

```sh
#!/bin/sh                                              # 指定脚本使用的 shell

rootdir=/compat/suse                                   # 定义 openSUSE 兼容层根目录
url="https://download.opensuse.org/repositories/Virtualization:/containers:/images:/openSUSE-Tumbleweed/container/opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz"  # openSUSE bootstrap 下载地址

echo "Starting openSUSE Tumbleweed installation..."        # 输出安装开始信息
echo "check modules ..."                               # 输出模块检查信息

# check linux module
if [ "$(sysrc -n linux_enable)" = "NO" ]; then         # 检查 Linux 内核模块是否启用
        echo "linux module should be loaded. Continue?(Y|n)"  # 提示是否继续
        read answer                                     # 读取用户输入
        case $answer in
                [Nn][Oo]|[Nn])
                        echo "linux module not loaded" # 提示模块未加载
                        exit 1                         # 退出脚本
                        ;;
                [Yy][Ee][Ss]|[Yy]|"")
                        sysrc linux_enable=YES         # 启用 Linux 内核模块
                        ;;
        esac
fi
echo "start linux"                                      # 输出启动 Linux 信息
service linux start                                     # 启动 Linux 内核模块

# check dbus
if ! /usr/bin/which -s dbus-daemon;then                # 检查 dbus-daemon 是否存在
        echo "dbus-daemon not found. install it [Y|n]" # 提示安装 dbus
        read answer                                     # 读取用户输入
        case $answer in
            [Nn][Oo]|[Nn])
                echo "dbus not installed"               # 提示未安装 dbus
                exit 2                                  # 退出脚本
                ;;
            [Yy][Ee][Ss]|[Yy]|"")
                pkg install -y dbus                     # 安装 dbus
                ;;
        esac
    fi

if [ "$(sysrc -n dbus_enable)" != "YES" ]; then        # 检查 dbus 服务是否启用
        echo "dbus should be enabled. Continue?(Y|n)"  # 提示是否启用
        read answer                                     # 读取用户输入
        case $answer in
            [Nn][Oo]|[Nn])
                        echo "dbus not running"       # 提示 dbus 未运行
                        exit 2                          # 退出脚本
                        ;;
            [Yy][Ee][Ss]|[Yy]|"")
                        service dbus enable            # 启用 dbus 服务开机自启
                        ;;
        esac
fi
echo "start dbus"                                       # 输出启动 dbus 信息
service dbus start                                      # 启动 dbus 服务

echo "now we will bootstrap openSUSE"                  # 输出 bootstrap 开始信息

fetch ${url}                                            # 下载 openSUSE bootstrap 压缩包
mkdir -p ${rootdir}                                    # 创建目标目录
tar xvf opensuse*.tar.xz -C ${rootdir} --numeric-owner  # 解压压缩包
rm opensuse-tumbleweed-dnf-image.x86_64-lxc-dnf.tar.xz  # 删除压缩包

if [ ! "$(sysrc -f /boot/loader.conf -qn nullfs_load)" = "YES" ]; then  # 检查 nullfs 是否加载
        echo "nullfs_load should load. continue? (Y|n)"  # 提示是否继续
        read answer                                     # 读取用户输入
        case $answer in
            [Nn][Oo]|[Nn])
                echo "nullfs is not loaded"                  # 提示 nullfs 未加载
		exit 3                                      # 退出脚本
                ;;
            [Yy][Ee][Ss]|[Yy]|"")
                sysrc -f /boot/loader.conf nullfs_load=yes  # 设置 nullfs 开机自启
                ;;
        esac
fi

if ! kldstat -n nullfs >/dev/null 2>&1;then            # 检查 nullfs 模块是否已加载
        echo "load nullfs module"                      # 输出加载信息
        kldload -v nullfs                               # 加载 nullfs 模块
fi

echo "mount some fs for linux"                         # 输出挂载文件系统信息
echo "devfs ${rootdir}/dev devfs rw,late 0 0" >> /etc/fstab        # devfs 挂载
echo "tmpfs ${rootdir}/dev/shm tmpfs rw,late,size=1g,mode=1777 0 0" >> /etc/fstab  # tmpfs 挂载
echo "fdescfs ${rootdir}/dev/fd fdescfs rw,late,linrdlnk 0 0" >> /etc/fstab        # fdescfs 挂载
echo "linprocfs ${rootdir}/proc linprocfs rw,late 0 0" >> /etc/fstab               # linprocfs 挂载
echo "linsysfs ${rootdir}/sys linsysfs rw,late 0 0" >> /etc/fstab                  # linsysfs 挂载
echo "/tmp ${rootdir}/tmp nullfs rw,late 0 0" >> /etc/fstab                         # /tmp 挂载
#echo "/home ${rootdir}/home nullfs rw,late 0 0" >> /etc/fstab                     # /home 挂载，可选
mount -al                                               # 挂载 fstab 中所有文件系统

echo "for openSUSE, we should change 'compat.linux.osrelease'. continue? (Y|n)"  # 提示修改 Linux 内核版本
read answer                                             # 读取用户输入
case $answer in
	[Nn][Oo]|[Nn])
		echo "close to success"                        # 提示跳过修改
		exit 4
		;;
	[Yy][Ee][Ss]|[Yy]|"")
		echo "compat.linux.osrelease=6.2.10" >> /etc/sysctl.conf  # 持久化设置 Linux 内核版本
		sysctl compat.linux.osrelease=6.2.10                     # 立即生效
                ;;
esac
echo "complete!"                                        # 输出完成信息
echo "to use: chroot ${rootdir} /bin/bash"             # 提示使用 chroot 进入 openSUSE 兼容层
echo ""
echo "I will set resolv.conf to Ali DNS"               # 提示将 DNS 设置为阿里 DNS
echo "continue?[Y|n]"                                   # 提示是否继续
read answer                                             # 读取用户输入
case $answer in
	[Nn][Oo]|[Nn])
		echo "set your openSUSE by yourself. Bye!"       # 提示用户自行配置
		exit 0
		;;
	[Yy][Ee][Ss]|[Yy]|"")
		echo "nameserver 223.5.5.5" >> ${rootdir}/etc/resolv.conf   # 设置 DNS

    echo "Now I will install opi, nano, tar, and vim"         # 输出安装软件信息
    chroot ${rootdir} /bin/bash -c "dnf update && dnf install -y opi nano tar vim"  # 安装常用软件
    
    echo "all done."                                      # 输出完成信息
    echo "Now you can run '#chroot /compat/suse/ /bin/bash' to enter openSUSE"  # 提示用户进入 openSUSE
		
                ;;
esac
```

## 故障排除与未竟事宜

### 当使用 LXC Leap 镜像时，zypper 不可用

同样的，当使用 openSUSE Tumbleweed 时，dnf 和 zypper 都不可用。该问题不影响我们的 leap 脚本，但是影响第二个风滚草的脚本，导致无法正常使用。已经报告 [Bug](https://bugzilla.opensuse.org/show_bug.cgi?id=1213158)。

例如，当尝试安装 Vim 时，你会遇到一个错误信息。

```sh
Installation of perl-5.36.1-1.2.x86_64 failed:
Error Subprocess failed Error: RPM failed: Command exited with status 1
```

在 LXC Leap 中，用 rpm 替换 rpm-ndb 可以解决该问题，dnf 不会出现类似问题。该问题仅存在于 zypper。然而，在 openSUSE Tumbleweed LXC 中，dnf 和 zypper 都存在该问题，替换 rpm-ndb 无法解决。

## 课后习题

1. 在 openSUSE Leap 兼容层中，用 rpm 替换 rpm-ndb 并分析为什么 zypper 会出现该问题而 dnf 不会，尝试解决使其共存。
2. 尝试修复 openSUSE Tumbleweed 兼容层中 dnf 和 zypper 的问题，分析该问题是否由 FreeBSD 兼容层对某些系统调用的不完整实现导致。
