FNOS 开启 SSH 后的服务器初始化

太阳作者太阳
原创内容采用 CC-4.0 协议发布,转载请注明出处
FNOSSSH服务器初始化CUDA

FNOS 是一套基于 Debian 发行版开发的 NAS 系统。我的机器除了存储数据,也会通过 SSH 跑一些开发服务和 GPU 容器。这篇文章记录的不是 FNOS 安装过程,而是系统已经装好、SSH 已经开启之后,我怎样把它整理成一台顺手的 Linux 服务器。

这些命令来自我平时维护这台 NAS 的清单。有些只在首次配置时执行一次,有些用于升级后的检查和故障处理。我把它们按用途拆开,避免下次只记得一条命令,却忘了它会改动哪里。

这里会改动 SSH、Swap 和 NVIDIA 驱动等系统配置。FNOS 仍然是一套完整的 NAS 系统,直接修改底层软件包可能影响系统更新、存储服务和 Docker 应用。先确认管理页面可以正常访问,重要数据已有备份,并且机器可以接显示器和键盘进行本地恢复。不要把全文做成一次性脚本执行。

开启 SSH

先在 FNOS 管理页面完成两项设置:

  1. 在「系统设置 → SSH」中开启 SSH 服务,按自己的网络环境设置端口。
  2. 在「系统设置 → 用户管理」中找到准备登录的管理员用户,为它启用 SSH 权限。

FNOS v1.1.19 的更新说明明确提到,升级后会同时关闭系统 SSH 服务和用户 SSH 登录权限。只打开第一处开关,客户端仍然无法登录。以后的系统升级如果出现 Connection refused 或登录后立即断开,我会先回管理页面检查这两项,而不是马上修改 sshd_config

然后从另一台电脑连接。正文中的 HOSTTARGET_USER 分别替换为 FNOS 的局域网地址和登录用户:

ssh -p 22 TARGET_USER@HOST

登录后先确认当前系统、用户和提权能力:

cat /etc/os-release
uname -a
id
sudo -v

后面的 CUDA 仓库以 Debian 12 x86_64 为例。VERSION_ID 或机器架构不同时,不要继续照抄对应章节。

补齐用户主目录

如果 SSH 登录时出现 Could not chdir to home directory,先查看账号记录中的主目录,而不是直接假定它一定是 /home/TARGET_USER

getent passwd "$USER"
printf 'HOME=%s\n' "$HOME"
ls -ld "$HOME"

确认目录确实不存在后再创建:

sudo mkhomedir_helper "$USER"
ls -ld "$HOME"

mkhomedir_helper 会按系统账号信息创建主目录并设置属主。目录已经存在时不要重复执行,更不要手工把别人的目录改成当前用户所有。

改用 SSH 公钥登录

先在客户端生成密钥。已有可用密钥时跳过这一步:

ssh-keygen -t ed25519 -a 100
ssh-copy-id -p 22 TARGET_USER@HOST

Windows 自带的 OpenSSH 客户端通常没有 ssh-copy-id,也可以把公钥内容追加到 FNOS 用户的 ~/.ssh/authorized_keys。服务端权限应当是:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

此时不要急着关闭密码登录。新开一个终端,确认下面的连接不再询问账号密码,并且 sudo 仍然可用:

ssh -p 22 TARGET_USER@HOST
sudo -v

确认成功后,再写一个独立的 SSH 配置片段:

sudo install -d -m 0755 /etc/ssh/sshd_config.d
sudo tee /etc/ssh/sshd_config.d/99-server-hardening.conf >/dev/null <<'EOF'
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
EOF

sudo sshd -t
sudo systemctl reload ssh
sudo sshd -T | grep -E '^(pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin) '

只有 sshd -t 没有输出、最后一条命令显示配置已经生效,才算完成。原来的 SSH 窗口先留着,再从新终端登录一次。这样即使配置有误,也能用旧会话撤销文件:

sudo rm /etc/ssh/sshd_config.d/99-server-hardening.conf
sudo systemctl reload ssh

如果 FNOS 后续升级重置了 SSH 配置,应以当时版本的管理页面和官方说明为准,不要为了保留这份配置阻止系统更新。

按需创建 Swap

内存充足、负载稳定的机器不一定需要额外 Swap。先看当前状态和系统盘空间:

free -h
swapon --show
df -h /

下面创建 4 GiB 的 /swapfile。它应放在系统盘,不要放进 FNOS 的存储池或共享目录:

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon --priority -1 /swapfile
swapon --show

确认启用正常后再写入 /etc/fstab,让重启后仍然生效:

grep -qE '^[[:space:]]*/swapfile[[:space:]]+' /etc/fstab || \
  echo '/swapfile none swap sw,pri=-1 0 0' | sudo tee -a /etc/fstab

sudo findmnt --verify

如果文件系统不支持 fallocate,可以改用较慢但兼容性更好的方式创建文件:

sudo dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress

使用 ZRAM 缓解内存压力

我的 FNOS 还会使用 ZRAM。它在内存里创建压缩交换设备,和磁盘上的 /swapfile 可以同时存在。先检查当前内核是否已经启用 ZRAM:

zgrep CONFIG_ZRAM /proc/config.gz 2>/dev/null || true
lsmod | grep zram || true
zramctl

需要配置时安装 systemd-zram-generator

sudo apt update
sudo apt install systemd-zram-generator
sudoedit /etc/systemd/zram-generator.conf

我使用下面这份配置,让 /dev/zram0 的逻辑容量等于物理内存的一半:

[zram0]
zram-size = ram / 2
compression-algorithm = lzo-rle

zram-size 是逻辑容量,不代表安装后会立刻占掉一半内存。写入的数据才会消耗内存。配置完成后重启,再检查设备、算法和实际占用:

sudo reboot
zramctl
swapon --show
systemctl status dev-zram0.swap --no-pager

如果内核不支持 lzo-rle,从下面的输出里选择已经列出的算法,或者删掉 compression-algorithm,使用内核默认值:

cat /sys/block/zram0/comp_algorithm

配置 Git 身份

服务器需要直接提交代码时再设置 Git 身份。公开文章不应放个人邮箱,下面使用占位值:

git config --global user.name "YOUR_NAME"
git config --global user.email "YOUR_EMAIL"
git config --global --list

如果机器只负责拉取和运行代码,这一步可以省略。仓库需要提交签名、代理或凭据时,也应按仓库单独配置,不要把访问令牌写进全局配置。

数据目录与软链接

FNOS 的数据盘通常挂载在 /vol1。我的开发目录和容器数据都放在数据盘,用户主目录只保留软链接。公开命令使用 STORAGE_ROOT 表示实际的数据目录:

STORAGE_ROOT=/vol1/TARGET_UID

test -d "$STORAGE_ROOT/develop" && ln -s "$STORAGE_ROOT/develop" "$HOME/develop"
test -d "$STORAGE_ROOT/volumes" && ln -s "$STORAGE_ROOT/volumes" "$HOME/volumes"
ls -ld "$HOME/develop" "$HOME/volumes"

如果目标路径已经存在,ln 会拒绝覆盖。先判断它是目录、文件还是旧链接,再决定怎么处理,不要直接加 -f

压缩备份与恢复

临时迁移 volumes 时,我会明确写出源目录和备份目录。这样不会漏掉隐藏文件,也不会把压缩包写进正在打包的目录:

SOURCE_DIR=/vol1/1000/volumes
BACKUP_DIR=/vol1/1000/backups
ARCHIVE="$BACKUP_DIR/volumes-$(date +%Y%m%d-%H%M%S).tar.gz"

mkdir -p "$BACKUP_DIR"
tar -czf "$ARCHIVE" -C "$SOURCE_DIR" .
tar -tzf "$ARCHIVE" | head

恢复时不要直接覆盖正在运行的服务目录。我会先解压到空目录,检查内容后再停服务并迁移:

RESTORE_DIR=/vol1/1000/restore-volumes

mkdir -p "$RESTORE_DIR"
tar -xzf "$ARCHIVE" -C "$RESTORE_DIR"
du -sh "$RESTORE_DIR"

压缩包能列出文件不等于备份可用。重要数据还需要定期做一次实际恢复,并放一份副本到另一块磁盘或另一台设备。

网络与 Wi-Fi

有线网络异常或临时需要无线连接时,可以用 NetworkManager 查看设备和热点:

nmcli device status
nmcli device wifi list

连接 Wi-Fi 时使用交互式输入,避免密码出现在脚本、Shell 历史和进程参数里:

nmcli --ask device wifi connect "WIFI_SSID"

连接后检查地址、路由和 DNS:

ip -brief address
ip route
resolvectl status

Docker 用户权限

如果需要让当前用户不加 sudo 运行 Docker,可以加入 docker 组:

sudo usermod -aG docker "$USER"
newgrp docker
docker info

docker 组可以控制 Docker daemon,实际权限接近 root。共享账号或不受信任的 SSH 用户不应加入这个组。

调整系统语言

遇到命令输出乱码、程序缺少所需 locale 时,我会重新选择系统语言:

locale
locale -a
sudo dpkg-reconfigure locales
locale

通常保留一个 UTF-8 locale 即可。修改后重新登录 SSH,让新的环境变量进入会话。

可选:Intel 核显与 IOMMU

使用 Intel 核显做媒体转码时,可以安装 VA-API 驱动和诊断工具:

sudo apt update
sudo apt install intel-media-va-driver-non-free intel-gpu-tools vainfo
vainfo
sudo intel_gpu_top

软件包是否存在取决于 FNOS 当前使用的 Debian 仓库。如果 apt 找不到包,先检查软件源,不要为了安装一个工具随意混用其他 Debian 版本的仓库。

虚拟机需要 PCIe 设备直通时,我才会启用 Intel IOMMU。先确认处理器和主板支持虚拟化,并在 BIOS 中开启 VT-d,然后编辑 GRUB:

sudoedit /etc/default/grub

把参数追加到已有的 GRUB_CMDLINE_LINUX_DEFAULT 中,不要覆盖原来的启动参数:

GRUB_CMDLINE_LINUX_DEFAULT="EXISTING_PARAMETERS intel_iommu=on iommu=pt"

生成启动配置并重启:

sudo update-grub
sudo update-initramfs -u -k all
sudo reboot

回来后确认参数和 IOMMU 分组已经出现:

cat /proc/cmdline
sudo dmesg | grep -Ei 'DMAR|IOMMU'
find /sys/kernel/iommu_groups/ -type l | head

启动失败时需要从 GRUB 临时删除新增参数,或者通过本地终端恢复 /etc/default/grub。远程机器没有带外管理时,我不会只靠 SSH 修改这一项。

可选:NVIDIA CUDA 与 Docker GPU

这一节只适用于装有受支持 NVIDIA 显卡、确实需要 CUDA 或 GPU 容器的机器。FNOS 使用定制内核,驱动模块能否构建和加载取决于当前内核、内核头文件与驱动版本。先记录现场:

lspci -nn | grep -i nvidia
uname -r
nvidia-smi || true
apt-cache policy "linux-headers-$(uname -r)"

如果 nvidia-smi 已经正常工作,就不要先清空现有驱动,也不要连续安装两个驱动分支。这样会中断正在使用 GPU 的服务,还可能让定制内核无法加载驱动模块。

以下仓库地址只对应 Debian 12 x86_64。执行前再次核对 /etc/os-releasedpkg --print-architecture

sudo apt update
sudo apt install -y ca-certificates curl wget gnupg

wget https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
rm cuda-keyring_1.1-1_all.deb
sudo apt update

先查看仓库实际提供的版本,再决定安装哪一个分支:

apt-cache search '^cuda-drivers-[0-9]+$'
apt-cache search '^cuda-toolkit-[0-9]+-[0-9]+$'

我目前使用 CUDA Toolkit 13.2。NVIDIA 的 13.2 发布说明要求 Linux 驱动不低于 595.45.04,因此示例使用 595 分支;实际安装前仍要确认 FNOS 内核、显卡型号和仓库中可用的软件包。这里的变量不是“永远最新”的答案:

DRIVER_BRANCH=595
CUDA_VERSION=13-2

sudo apt install "cuda-drivers-${DRIVER_BRANCH}" "cuda-toolkit-${CUDA_VERSION}"
sudo reboot

如果驱动包已经安装,但内核升级后 nvidia-smi 失效,可以先确认 Debian 包状态,再尝试重新安装。这里不是首次安装步骤:

dpkg -l | grep -E 'nvidia-driver|nvidia-driver-cuda'
sudo apt install --reinstall nvidia-driver nvidia-driver-cuda
sudo reboot

批量 purge '^nvidia-.*' '^libnvidia-.*' '^cuda-.*' 只留作彻底重装时的最后手段。执行前应停止 GPU 容器、记录已安装包,并先用 apt-get --simulate purge ... 查看会被删除的内容。

重启后验证驱动和编译工具:

nvidia-smi
/usr/local/cuda/bin/nvcc --version

如果 nvcc 没有进入当前用户的 PATH,再把配置追加到 ~/.bashrc,并避免重复写入:

grep -qxF 'export CUDA_HOME=/usr/local/cuda' ~/.bashrc || echo 'export CUDA_HOME=/usr/local/cuda' >> ~/.bashrc
grep -qxF 'export PATH=$CUDA_HOME/bin:$PATH' ~/.bashrc || echo 'export PATH=$CUDA_HOME/bin:$PATH' >> ~/.bashrc
grep -qxF 'export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH' ~/.bashrc || echo 'export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc

要让 FNOS 的 Docker 使用 GPU,还需要 NVIDIA Container Toolkit。按照 NVIDIA 当前的 Debian 安装方式添加独立仓库:

sudo apt-get update
sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg2

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
  sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg

curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

nvidia-ctk 会修改 /etc/docker/daemon.json,重启 Docker 也会短暂中断正在运行的容器。先安排维护时间并备份配置:

sudo test ! -f /etc/docker/daemon.json || \
  sudo cp -a /etc/docker/daemon.json /etc/docker/daemon.json.before-nvidia
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

最后用一个临时容器验证 GPU。CUDA 镜像标签会被 NVIDIA 定期更新或移除,执行前应在 NGC 的支持标签中确认它仍然存在,并检查主机驱动是否满足对应 CUDA 版本:

docker run --rm --gpus all nvidia/cuda:13.2.0-base-ubuntu24.04 nvidia-smi

PostgreSQL 检查

FNOS 上的应用数据库出现问题时,我先看服务状态和近期日志:

systemctl list-units --type=service | grep -i postgres
sudo journalctl -u postgresql -r -n 100 --no-pager

服务名称可能不是 postgresql,应使用第一条命令查到的实际 unit。需要进入数据库时:

sudo -u postgres psql

psql 中可以查看角色,并用交互方式修改密码:

\du
\password postgres
\q

我不会执行查询系统表来读取密码哈希,也不会把新密码写进 Shell 命令或文章。

用 snitch 查看网络连接

snitch 是一个把 ssnetstat 结果整理成终端界面的第三方工具。直接运行远程安装脚本很方便,但脚本内容会随仓库更新。我会先下载并阅读,再执行本地文件:

curl -fsSLo /tmp/install-snitch.sh \
  https://raw.githubusercontent.com/karol-broda/snitch/master/install.sh
less /tmp/install-snitch.sh
sh /tmp/install-snitch.sh
rm /tmp/install-snitch.sh

安装后常用的是监听端口和已建立连接:

snitch -l
snitch ls -l
snitch ls -t -e

清理 APT 缓存

系统盘空间紧张时,我会先看占用,再清理已下载的软件包:

df -h /
sudo du -sh /var/cache/apt /var/lib/apt/lists
sudo apt-get clean
sudo apt-get autoclean

autoremove 可能删掉内核、驱动或其他被判断为不再需要的依赖。先预览,不加 -y

sudo apt-get autoremove

我不再把 rm -rf /var/lib/apt/lists/* 当作日常清理命令。它只会删除软件源索引,下次仍要重新执行 apt update,节省的空间通常不值得多一次手工处理。

日常复查

这套流程做完后,我会保留几条短命令,用来判断系统升级有没有改变运行状态:

systemctl is-active ssh docker
sudo ss -lntp | grep sshd
swapon --show
zramctl
df -h / /vol1
nmcli device status
sudo sshd -t
nvidia-smi

没有 NVIDIA 显卡时,最后一条自然不需要执行。机器没有 NetworkManager 或 /vol1 使用了其他挂载点时,也要删掉对应命令。FNOS 升级后则优先检查管理页面中的 SSH 服务和用户权限,再检查自定义 SSH 配置、Swap、ZRAM、数据盘、Docker 与 GPU,不把所有故障都归到网络上。

参考资料