面向教室与创客空间:使用 LTSP 替代 PiServer 搭建树莓派无盘课堂环境


一、前言

之前我一直使用 PiServer 来做为创客空间的树莓派设备部署方案,有很多好处,不需要使用 SD 卡、统一系统镜像、集中管理学生账户,所有人的 home 文件夹都挂载在 Server 上,学生可以使用任一台树莓派登录自己的账户,就可以看到自己的文件内容。

Githun 上的 PiServer PiServer 虽然好用,它有图形界面,便于设置使用,但是由于它太久没有维护更新,当我把 Server 树莓派上的系统升级为 Raspberry Pi OS Trixie 后,它无法正常工作了,不得已,我不得不尝试寻找一个 PiServer 的替代方案,这就是本文的初衷。

后来我发现了 LTSP,与 PiServer 不同,LTSP 没有图形界面,在使用方面没有那么便捷,而且它也不是专为树莓派设计的,需要修改很多的配置,还会遇到一些问题。 本篇文章就是记录我的完整部署过程,希望可以给你提供一些有价值的参考。

在你开始部署之前,建议你先看一些本文的 [[面向教室与创客空间:使用 LTSP 替代 PiServer 部署树莓派无盘课堂环境#五、问题与排错]] 部分,再决定要不要开始。

二、整体架构概览

graph LR
    subgraph Internet
        WAN[互联网]
        WAN --- RA[路由器 A]
    end

    subgraph LAN[教室内局域网]
	    RA --- RB[路由器 B]
	    RB --- SW[交换机(可选)]
        RB --- RC[路由器 C(可选)]

        SW --- S[树莓派 Server]
        SW --- C1[树莓派 Client 1]
        SW --- C2[树莓派 Client 2]
        SW --- C3[树莓派 Client n...]
    end

其中:

  • 路由器 A 正常接入互联网。
  • 路由器 B 是一台多网线插口的路由器,传输速率越高,树莓派 Client 启动速度越快,本路由器需要关闭 DHCP 功能,所以不建议省略路由器 A,为了不影响其他设备联网,建议通过路由器 B 把教室内的设备组成单独的局域网。
  • 交换机 为可选设备,全部树莓派必须使用网线连接,不可以使用 Wi-Fi,当路由器 B 接口不够用时可以加入交换机
  • 路由器 C 为可选设备,当有其他设备需要 Wi-Fi 连接,而路由器 B 又没有 Wi-Fi 功能时可以额外加入一台带有 Wi-Fi 功能的路由器 C
  • 树莓派 Server 应该使用安装了 SSD 的树莓派 5,这里因为读写速度限制,不应该使用 SD 卡作为树莓派 Server 的硬盘。
  • 树莓派 Client 可以是树莓派 4/5,理论上树莓派 3 也可以,不过我没有测试,而且树莓派 3 与树莓派 4/5 相比需要额外的配置。

三、准备工作

硬件部分
  • 树莓派 5 一台作为树莓派 Server
  • 树莓派 M.2 HAT+ 扩展版和与之适配的 SSD 硬盘。
  • 其他作为 Client 的树莓派们,设置好 Network Boot。
  • 路由器和交换机等,参考 二、整体架构概览 部分。
软件部分
  • 树莓派 Server 安装操作系统,Raspberry Pi OS Trixie 64-bit,建议提前下载好镜像文件,然后使用 Raspberry Pi Imager 烧录到 SSD,这样可以保留镜像文件在为树莓派 Client制作 Chroot 时使用。
  • 提前下载好用于树莓派 Client 的系统镜像文件,我使用的是和树莓派 Server 相同的Raspberry Pi OS Trixie 64-bit 系统。
网络部分
  • 路由器 B 设置静态 IP:192.168.5.1,当然你也可以设置成你自己的 IP,只不过后面涉及到 IP 地址的命令会以我的设置为例,如果你设置成和我相同的 IP,复制命令时会很方便,不需修改。
  • 树莓派 Server 编辑网络设置,IPv4 设置为手动(Manual),添加条目 192.168.5.2 | 24 | 192.168.5.1 DNS:192.168.5.1。
  • 路由器 B 关闭 DHCP 功能。
  • 所有树莓派设备与路由器或交换机连接必须使用网线。

四、部署过程

以下步骤是在 LTSP 官方文档的基础上,加入了我自己的实践。

1. 切换 root 用户

以下步骤中的命令都应该使以 root 身份执行,所以先运行:

sudo -i
2. 添加软件包仓库

官方推荐,可以获取 LTSP 最新的稳定版。

wget https://ltsp.org/misc/ltsp-ubuntu-ppa-focal.list -O /etc/apt/sources.list.d/ltsp-ubuntu-ppa-focal.list
wget https://ltsp.org/misc/ltsp_ubuntu_ppa.gpg -O /etc/apt/trusted.gpg.d/ltsp_ubuntu_ppa.gpg
apt update
3. 安装 LTSP server 软件包
apt install --install-recommends ltsp ltsp-binaries dnsmasq nfs-kernel-server openssh-server squashfs-tools ethtool net-tools epoptes
gpasswd -a administrator epoptes

请将 administrator 替换为实际的管理员用户名。
如果你未使用 PPA,请将 ltsp-binaries 替换为 ipxe。 上述软件包说明

ltsp 包含 LTSP 主体代码,是 LTSP 服务器和 LTSP 客户端共同使用的核心组件。

ltsp-binaries 包含 iPXE 与 memtest 的二进制文件。

dnsmasq 提供 TFTP,以及可选的(代理)DHCP 与 DNS 服务。
可替代方案包括 isc-dhcp-servertftpd-hpa,但只有 dnsmasq 支持 proxyDHCP,因此为推荐默认选项。

nfs-kernel-server 通过 NFS 向客户端导出虚拟磁盘映像。

openssh-server 允许客户端通过 SSHFS 认证并访问 /home

ethtool、net-tools 可用于禁用以太网流控(Ethernet flow control),在服务器是千兆网、客户端为百兆网时可提升局域网性能。

epoptes(可选) 提供客户端监控与远程控制功能。
gpasswd 用于赋予系统管理员运行 Epoptes 的权限。

4. 准备用于 Client 的 Chroot

将下载好的 Raspberry Pi OS 镜像压缩文件拷贝到 server 上,打开终端,在同一目录里运行解压命令:

xz -dk 2025-11-24-raspios-trixie-arm64-full.img.xz

你要把文件名换成你自己下载的镜像压缩文件,解压完成后你会得到一个同名的 .img 镜像文件。 然后运行以下命令,别忘了切换 root 身份,别忘了切换到镜像文件所在目录。

# 安装 time,可选。
apt install time
# 创建 chroot 根目录。
mkdir -p /srv/ltsp/raspios
# 挂载镜像并拷贝 root 文件,如果挂载失败可以尝试修改 loop8 为其他数字。
losetup -rP /dev/loop8 2025-11-24-raspios-trixie-arm64-full.img
mount -o ro /dev/loop8p2 /mnt
time cp -a /mnt/. /srv/ltsp/raspios
umount /mnt
# 如果不删除 boot 文件夹,后面的拷贝命令无法顺利执行。
rm -r /srv/ltsp/raspios/boot
# 挂载并拷贝 boot 文件。
mount -o ro /dev/loop8p1 /mnt
cp -a /mnt/. /srv/ltsp/raspios/boot/
umount /mnt
losetup -d /dev/loop8

此时,Raspberry Pi OS 应当已经位于 /srv/ltsp/raspios
但该 chroot 仍未准备好用于网络启动,需要执行以下命令:

# 进入 chroot,以便使用相对路径
cd /srv/ltsp/raspios
# 屏蔽在 chroot 环境中不需要的服务,屏蔽的服务将来在 Client 端不会运行。
systemctl mask --root=. dhcpcd dphys-swapfile raspi-config resize2fs_once NetworkManager-wait-online
# 移除与 SD 卡相关的 fstab 条目
echo 'proc /proc proc defaults 0 0' >./etc/fstab
# 为 NFS_RW 网络启动模式写入适用的内核命令行参数
echo 'ip=dhcp root=/dev/nfs rw nfsroot=192.168.5.2:/srv/ltsp/raspios,vers=3,tcp,nolock console=serial0,115200 console=tty1 elevator=deadline fsck.repair=yes rootwait quiet splash plymouth.ignore-serial-consoles modprobe.blacklist=bcm2835_v4l2' >./boot/cmdline.txt
5. 准备 Server
dnsmasq

使用以下命令生成默认的 dnsmasq 配置文件。

ltsp dnsmasq

修改 /etc/dnsmasq.d/ltsp-dnsmasq.conf 文件。以下是全部需要修改或新增的地方,默认配置文件中的其他部分不需要改动。

# IP ranges to hand out, usually on the internal LTSP subnet of 2-NIC setups
dhcp-range=192.168.5.101,192.168.5.200,12h

# Specify the DNS server. 0.0.0.0 means the machine running dnsmasq.
# DNS_SERVER in ltsp.conf is preferred as it reaches proxy DHCP clients.
dhcp-option=option:dns-server,192.168.5.1
dhcp-option=option:router,192.168.5.1

#这里填入全部 Client 设备的 Mac 地址,每行一个。
dhcp-mac=set:rpi,d8:3a:*:*:*:*
ltsp.conf

使用以下命令生成 ltsp.conf 文件

install -m 0660 -g sudo /usr/share/ltsp/common/ltsp/ltsp.conf /etc/ltsp/ltsp.conf

/etc/ltsp/ltsp.conf 中添加以下内容:

[server]
# ltsp kernel 会依据该名称,从 /srv/ltsp/raspios/boot/* 生成指向 /srv/tftp/* 的相应符号链接。
RPI_IMAGE="raspios"
# 通过 NFS 挂载 /home。
NFS_HOME=1

[clients]
# 配置文件中已有,取消注释即可。通过 NFS 挂载 /home。
FSTAB_HOME="server:/home /home nfs defaults,nolock 0 0"
# 用于隐藏登陆界面中的用户名列表
LIGHTDM_CONF=”greeter-hide-users=true“
初始化

接下来,运行以下构建命令:

ltsp kernel raspios
ltsp initrd
ltsp nfs
6. 以 NFS_RW 模式从网络启动

此时,我们已经可以以 NFS_RW(读写 NFS)模式启动单个客户端。 这意味着在该客户端上进行的任何更改,如安装新软件,都会直接保存到服务器的 /srv/ltsp/raspios 中。 首先,以 NFS 可读写模式发布该 chroot:

echo '/srv/ltsp/raspios *(rw,async,crossmnt,no_subtree_check,no_root_squash,insecure)' >/etc/exports.d/ltsp-raspios.exports
exportfs -ra

现在启动一台 Client,向其软件源中 添加 LTSP PPA(参考[[面向教室与创客空间:使用 LTSP 替代 PiServer 部署树莓派无盘课堂环境#2. 添加软件包仓库]]),并安装客户端所需的软件包:

apt install --install-recommends ltsp epoptes-client

修改 /etc/hosts 文件,添加以下内容:

sudo nano /etc/hosts
192.168.5.2        server

从服务器获取 OpenSSL 证书

sudo epoptes-client -c

一切正常的话,你可以看到 Successfully

接下来你可以在 Client 安装需要的软件,比如,输入法,进行一些设置,比如,键盘布局,语言地区。

Client 设置好以后,关机。

7. 切换到 LTSP 模式启动

此时,chroot 中已经包含 LTSP 代码,并已具备网络启动的条件。
但它所需的内核命令行与 NFS_RW 模式不同,因此请执行以下命令:

echo 'ip=dhcp root=/dev/nfs nfsroot=192.168.5.2:/srv/ltsp/raspios,vers=3,tcp,nolock init=/usr/share/ltsp/client/init/init ltsp.image=images/raspios.img console=serial0,115200 console=tty1 elevator=deadline fsck.repair=yes rootwait quiet splash plymouth.ignore-serial-consoles modprobe.blacklist=bcm2835_v4l2' >/srv/ltsp/raspios/boot/cmdline.txt

# 最后,制作只读镜像。
ltsp image raspios --mksquashfs-params='-comp lzo'
8. 添加用户

在 Server 端使用命令添加用户,即可在 Client 端登录。

adduser user1

添加用户后,需要重新执行构建命令,不需要重新生成镜像。用户修改密码可以在 Server 端,也可以在 Client 端执行,修改密码后需要执行构建命令。

ltsp kernel raspios
ltsp initrd
ltsp nfs

五、问题与排错

以下是目前我发现的一些问题,以及部分问题的解决办法,希望可以让你少走些弯路,也可以先看一下这部分问题,你是否能够接受,再决定要不要开始部署 LTSP。

问题 1 Scratch 中与 GPIO 相关的扩展积木问题
问题描述

在 Client 端使用 Scratch 时,当添加或运行与 GPIO 相关的扩展积木时,例如,扩展 Simple Electronics 中的 When button 0 is pressed,会出现 Scratch 白屏崩溃的情况。

解决办法

这一版是由于 Client 端登录的用户的权限不正确导致的,在 Server 端创建的用户默认不属于 gpio 用户组,需要将用户加入组。 先在 Server 或 Client 上检查用户是否在 gpio 组:

id user

你需要看到里面包含:

... groups=...,20(dialout),29(audio),44(video),... ,997(gpio),...

如果没有 gpio (以及你想用 I2C/SPI 也最好在 i2cspi),在 Server 上执行:

sudo usermod -aG gpio,i2c,spi 你的用户名

添加用户组后执行构建命令。

问题 2 epoptes 无法启动
问题描述

在 Server 端打卡 epoptes 时没有任何反应。

解决办法

在 Server 终端中运行 epoptes,

epoptes

查看终端中是否出现错误信息:

user@raspberrypi:~$ epoptes cert
Traceback (most recent call last):
  File "/usr/bin/epoptes", line 20, in <module>
    from epoptes.ui import gui
  File "/usr/lib/python3/dist-packages/epoptes/ui/gui.py", line 8, in <module>
    from distutils.version import LooseVersion
ModuleNotFoundError: No module named 'distutils'

如果出现此错误信息,说明是由于 Python 版本不适配导致的,可以通过直接修改 epoptes 源码来解决。把 /usr/lib/python3/dist-packages/epoptes/ui/gui.py 里对 distutils.versionpipes 的依赖换掉。

  1. 打开文件(路径来自你的报错栈):
sudo nano /usr/lib/python3/dist-packages/epoptes/ui/gui.py
  1. 使用 Ctrl+F 查找文件中的 pipes 并全部替换成 shlex 共有两个地方需要修改,其中之一是 import 部分,另一个是 pipes.xxx 方法。

  2. 保存并退出。

现在 epoptes 可正常运行。

问题 3 epoptes 中无法广播屏幕
问题描述

在 Server 端无法向 Client 广播屏幕,也无法查看 Client 端屏幕。这是由于安装的 TigerVNC 1.15.0 不支持树莓派的 Wayland 服务。理论上安装 TigerVNC 1.60.0 beta 应该可以解决,不过我还没有尝试。

问题 4 Client 启动时卡在 wayvnc-control.service
问题描述

当 Client 启动时会会卡咋某些 service 上,停留时间比较久,虽然最终可以进入系统,但是耗时较长,整个过程大约需要数分钟,当然可能与我的网线和路由器网口速率比较慢有一定关系,但是 wayvnc-control.service 和 cloud-init.service 卡住较长时间不是网络速率慢的原因,我没有尝试在生成 chroot 镜像时屏蔽这些卡住的 service,因为我担心带来更多的问题。目前没有好的解决办法。