Wayland 无人值守远程桌面 — Sunshine 方案完整指南 环境
节点
系统
桌面
GPU
角色
家里电脑
Ubuntu 26.04
GNOME (Wayland)
NVIDIA RTX 4060 Ti
被控端
雨云服务器
Ubuntu Server
无 GUI
—
SSH 中转
公司电脑
Ubuntu
GNOME
—
控制端
为什么选 Sunshine 而不是 GNOME RDP
GNOME RDP
Sunshine
编码
CPU 软件编码,延迟高
GPU 硬编码 (NVENC/VA-API),延迟低
凭据
依赖 gnome-keyring,自动登录后丢失,每次重启需手动重设
独立存储,通过 --creds 命令行注入
锁屏
锁屏后 PipeWire 截屏管道切断,远程黑屏
PipeWire 管道不受锁屏影响
画质
软件编码,高分辨率吃力
HEVC/AV1 硬编码,2560×1440 流畅
重启表现
keyring 锁定 → 凭据失效 → 连不上
systemd + ExecStartPre 注入凭据,开机自恢复
Sunshine 核心概念(必读) 端口分工 1 2 3 4 5 6 47985 — 内部探测端口,不需要隧道 47989 — Moonlight 控制端口(TCP) 47990 — 串流 HTTPS 端口(TCP),核心 47991 — Web UI 管理端口(HTTPS,配对用) 47998-48010 — 视频/音频 RTSP 数据流(TCP,force_tcp=true 时) 48011 — RTSP 信令端口(TCP)
全部 TCP 端口(force_tcp = true),均可通过 SSH 隧道转发。
凭据存储与 503 错误 Sunshine 的 Web UI 用户名密码和 Moonlight 配对令牌都存储在 sunshine_state.json 中。
如果手动删除 sunshine_state.json → 凭据丢失 → Moonlight 收到 503
如果 Sunshine 进程重启时有旧进程未退出 → 端口冲突 → 部分功能失败 → 503
解决 :systemd 服务中使用 ExecStartPre 在启动前(1)杀掉残留进程(2)自动注入凭据。
Systemd 竞态条件 1 2 3 4 5 6 systemd restart sunshine.service → 发 SIGTERM 给旧进程 → 旧 Sunshine 正在释放 GPU、关闭端口(需要 2-3 秒) → 新 Sunshine 同时启动 → 尝试绑定 48011 → 旧进程还占着 → "地址已在使用" → 崩溃
解决 :ExecStartPre=/bin/bash -c 'pkill -9 sunshine 2>/dev/null; sleep 2; true'
整体架构 1 2 3 4 5 6 7 8 9 10 11 12 13 ┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐ │ 家里 GNOME │ │ 雨云服务器 │ │ 公司 GNOME │ │ │ autossh -R │ │ ssh -L │ │ │ Sunshine ◄─────────┼───────────────► │ localhost ◄──────────┼────────────────│ Moonlight │ │ 47989 47990 47991 │ SSH 隧道内 │ 11 个端口 │ SSH 隧道内 │ localhost │ │ 47998-48011 │ │ │ │ │ └─────────────────────┘ └──────────┬───────────┘ └─────────────────────┘ │ NAT 映射 <服务器公网IP>:<服务器SSH端口> 公网只暴露 1 个 SSH 端口。 所有 Sunshine/Moonlight 端口在 SSH 隧道内部传输,NAT 用量 = 1/10。
第一部分:家里电脑配置 以下所有操作在家里的 Ubuntu 终端 中执行。
1.0 SSH 密钥免密登录(必须先做) systemd 后台服务没有终端输入密码,必须配置密钥免密登录。
1 2 ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" ssh-copy-id -p <服务器SSH端口> <服务器用户名>@<服务器公网IP>
验证:
1 ssh -p <服务器SSH端口> <服务器用户名>@<服务器公网IP> "echo ok"
看到 ok 即完成。
1.1 自动登录 Wayland 下 Sunshine 需要用户登录桌面会话后才能访问 PipeWire 截屏管道。
1 sudo vim /etc/gdm3/custom.conf
确保 [daemon] 段下只有:
1 2 3 [daemon] AutomaticLoginEnable = true AutomaticLogin = <本地用户名>
⚠️ 如果文件已有 [daemon] 段,手动编辑,不要用 tee -a 追加(重复段会互相覆写)。
1.2 关闭锁屏与息屏 1 2 3 gsettings set org.gnome.desktop.screensaver lock-enabled false gsettings set org.gnome.desktop.session idle-delay 0 gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
1.3 安装 Sunshine(Flatpak 版) Ubuntu 26.04 与 Sunshine 24.04 deb 包 ICS 库版本不兼容(libicuuc.so.70 缺失)。Flatpak 版自带所有依赖,不受系统版本影响。
1 2 3 sudo apt install -y flatpakflatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo flatpak install -y flathub dev.lizardbyte.app.Sunshine
授予 Wayland 和 GPU 访问权限:
1 flatpak override --user --socket=wayland --device=dri dev.lizardbyte.app.Sunshine
权限
作用
--socket=wayland
访问 Wayland compositor,用于 PipeWire 截屏
--device=dri
访问 GPU 渲染节点(NVENC 编码必需)
1.4 配置 Sunshine 1 2 3 4 5 6 7 8 9 cat > ~/.var/app/dev.lizardbyte.app.Sunshine/config/sunshine/sunshine.conf << 'EOF' adapter_name = /dev/dri/renderD128 encoder = nvenc output_name = 0 port = 47990 upnp = disabled force_tcp = true platform = wayland EOF
配置详解:
参数
值
为什么这样设
adapter_name
/dev/dri/renderD128
Wayland 下 KMS/DRM 抓帧的渲染节点。NVIDIA 闭源驱动 + Wayland 下 KMS 模式可能失败(503),改用 PipeWire(见 platform)
encoder
nvenc
RTX 4060 Ti 的硬件编码器,比 CPU 软件编码快 10 倍
output_name
0
使用第一个显示器
port
47990
串流默认端口,保持默认即可
upnp
disabled
禁用 UPnP 端口映射。我们走 SSH 隧道,不需要路由器开洞
force_tcp
true
强制 TCP 模式。SSH 隧道只转发 TCP,UDP 无法穿透。开启后所有视频/音频流走 TCP,延迟略高但可靠
platform
wayland
使用 PipeWire 管道而不是 KMS/DRM。NVIDIA 闭源驱动 + Wayland + KMS 组合已知兼容性问题(”failed to initialize video capture”),PipeWire 更稳定
1.5 Sunshine systemd 服务(含凭据注入 + 竞态修复) 这是整个方案最关键的配置。systemd 服务在启动 Sunshine 之前,会自动清理残留进程并注入凭据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 cat > ~/.config/systemd/user/sunshine.service << 'EOF' [Unit] Description=Sunshine (Flatpak) - Moonlight 串流服务 After=network-online.target Wants=network-online.target [Service] ExecStartPre=/bin/bash -c 'pkill -9 sunshine 2>/dev/null; sleep 2; true' ExecStartPre=/usr/bin/flatpak run --command =sunshine dev.lizardbyte.app.Sunshine --creds <你的用户名> '<你的密码>' ExecStart=/usr/bin/flatpak run dev.lizardbyte.app.Sunshine Restart=always RestartSec=5 [Install] WantedBy=default.target EOF systemctl --user daemon-reload systemctl --user enable --now sunshine.service sleep 4systemctl --user status sunshine.service | head -5 ss -tlnp | grep sunshine
预期看到 4-5 条 LISTEN 端口(47985, 47990, 47991, 48011 等)。
ExecStartPre 详解:
行
作用
解决的问题
pkill -9 sunshine; sleep 2
彻底清场再启动
Systemd restart 竞态:旧进程未退出时新进程启动,端口冲突崩溃
flatpak run --creds
每次启动注入凭据
删除 sunshine_state.json 重置配对后凭据丢失 → 503
1.6 SSH 反向隧道 systemd 服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 sudo apt install -y autosshmkdir -p ~/.config/systemd/usercat > ~/.config/systemd/user/rdp-tunnel.service << 'DELIM' [Unit] Description=Sunshine 反向隧道到雨云服务器 After=network-online.target Wants=network-online.target [Service] ExecStart=/usr/bin/autossh \ -M 0 -N -p <服务器SSH端口> \ -R 47989:localhost:47989 \ -R 47990:localhost:47990 \ -R 47991:localhost:47991 \ -R 47998:localhost:47998 \ -R 47999:localhost:47999 \ -R 48000:localhost:48000 \ -R 48002:localhost:48002 \ -R 48004:localhost:48004 \ -R 48006:localhost:48006 \ -R 48008:localhost:48008 \ -R 48010:localhost:48010 \ -R 48011:localhost:48011 \ -o ServerAliveInterval=30 \ -o ExitOnForwardFailure=yes \ <服务器用户名>@<服务器公网IP> Restart=always RestartSec=10 [Install] WantedBy=default.target DELIM systemctl --user daemon-reload systemctl --user enable --now rdp-tunnel.service systemctl --user status rdp-tunnel.service | head -3
共 12 个端口,全部在 SSH 隧道内部传输,不增加 NAT 用量。
1.7 家里配置总验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 systemctl --user status sunshine.service | head -3 ss -tlnp | grep sunshine systemctl --user status rdp-tunnel.service | head -3 ssh -p <服务器SSH端口> <服务器用户名>@<服务器公网IP> "ss -tlnp | grep 127.0.0.1 | grep -c 47" sudo reboot
第二部分:服务器配置
2.1 SSH 心跳保活(必须配) 家里电脑异常断电时,TCP 连接不会立刻断开,SSH 子进程变成僵尸占着端口。心跳保活确保 3 分钟内自动回收。
1 2 3 4 5 6 7 8 ssh -p <服务器SSH端口> <服务器用户名>@<服务器公网IP> "sudo tee -a /etc/ssh/sshd_config" << 'EOF' ClientAliveInterval 60 ClientAliveCountMax 3 EOF ssh -p <服务器SSH端口> <服务器用户名>@<服务器公网IP> "sudo systemctl restart sshd"
每 60 秒发心跳,连续 3 次无响应 = 180 秒后断开并释放端口。
2.2 断线恢复全景
场景
家里
服务器
公司
恢复
家里网络抖动(< 30s)
心跳保活,不丢连接
无感知
无感知
✅ 自动
家里宽带断(> 3min)
autossh 断开,10s 后重试
心跳超时,3min 后端口释放
Moonlight 断开
✅ 网络恢复后 autossh 自动重连,公司重开 Moonlight
家里断电/重启
systemd 同时重启 sunshine + autossh
心跳超时释放端口
Moonlight 断开
✅ 来电后全自动恢复(凭据由 ExecStartPre 注入)
服务器重启
ssh 全断,autossh 10s 重试
sshd 重新拉起
Moonlight 断开
✅ 服务器 sshd 启动后 autossh 自动重连
公司网络断开
家里↔服务器 链路正常
隧道正常
Moonlight 断开
🔧 公司网络恢复后重开 ssh -L + Moonlight
2.3 端口监控脚本(可选) 1 2 3 4 5 6 7 8 9 10 11 12 ssh -p <服务器SSH端口> <服务器用户名>@<服务器公网IP> "sudo tee /usr/local/bin/check-tunnel.sh" << 'SCRIPT' EXPECTED=10 ACTUAL=$(ss -tlnp | grep -c '127.0.0.1:47\|127.0.0.1:48' ) if [ "$ACTUAL " -lt "$EXPECTED " ]; then echo "[$(date) ] WARNING: Tunnel ports degraded: $ACTUAL /$EXPECTED " >> /var/log/tunnel-monitor.log fi SCRIPT ssh -p <服务器SSH端口> <服务器用户名>@<服务器公网IP> "sudo chmod +x /usr/local/bin/check-tunnel.sh" ssh -p <服务器SSH端口> <服务器用户名>@<服务器公网IP> \ "(crontab -l 2>/dev/null; echo '* * * * * /usr/local/bin/check-tunnel.sh') | crontab -"
登录服务器后 cat /var/log/tunnel-monitor.log 查看历史异常。
2.4 手动清理残留僵尸 1 ssh -p <服务器SSH端口> <服务器用户名>@<服务器公网IP> "pkill -f 'sshd.*:47\|sshd.*:48'"
配了心跳保活(2.1)后通常不需要手动操作。
第三部分:公司电脑配置
3.1 安装 Moonlight 1 sudo apt install -y moonlight-qt
3.2 首次配对(只需做一次) ① 确认家里就绪:
1 2 ssh -p <服务器SSH端口> <服务器用户名>@<服务器公网IP> "ss -tlnp | grep -c 47990"
② 开正向隧道:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ssh -N -p <服务器SSH端口> \ -L 47989:127.0.0.1:47989 \ -L 47990:127.0.0.1:47990 \ -L 47991:127.0.0.1:47991 \ -L 47998:127.0.0.1:47998 \ -L 47999:127.0.0.1:47999 \ -L 48000:127.0.0.1:48000 \ -L 48002:127.0.0.1:48002 \ -L 48004:127.0.0.1:48004 \ -L 48006:127.0.0.1:48006 \ -L 48008:127.0.0.1:48008 \ -L 48010:127.0.0.1:48010 \ -L 48011:127.0.0.1:48011 \ <服务器用户名>@<服务器公网IP>
命令会”卡住”——这是正常的,-N 不返回 shell,隧道已建立。保持这个终端窗口开着。
③ 打开 Moonlight:
④ 添加主机:
点左上角 + → 地址填 127.0.0.1 → 确定
Moonlight 弹出一个 4 位 PIN 码
⑤ 确认配对:
浏览器打开 https://127.0.0.1:47991
首次访问提示证书不安全 → 点「高级」→「继续访问」
登录(1.5 中 --creds 设置的用户名和密码)
点顶部 PIN 标签 → 输入 Moonlight 显示的 4 位 PIN → 点 Send
Moonlight 显示配对成功,出现家里电脑桌面图标
⚠️ 注意方向:是 Moonlight → 弹出 PIN → Sunshine Web UI 填入 ,不是反过来。
⑥ 双击电脑图标 → 连接成功。
3.3 后续每次连接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ssh -N -p <服务器SSH端口> \ -L 47989:127.0.0.1:47989 -L 47990:127.0.0.1:47990 \ -L 47991:127.0.0.1:47991 -L 47998:127.0.0.1:47998 \ -L 47999:127.0.0.1:47999 -L 48000:127.0.0.1:48000 \ -L 48002:127.0.0.1:48002 -L 48004:127.0.0.1:48004 \ -L 48006:127.0.0.1:48006 -L 48008:127.0.0.1:48008 \ -L 48010:127.0.0.1:48010 -L 48011:127.0.0.1:48011 \ <服务器用户名>@<服务器公网IP> moonlight-qt &
3.4 Moonlight 推荐设置
设置项
值
位置
视频编解码器
HEVC
高级设置
视频解码器
硬件解码
高级设置
自动检测被阻止的连接
✅ 勾选
高级设置
第四部分:完整开机链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 开机卡通电 → BIOS POST → GRUB → GDM 自动登录 (<本地用户名>) → GNOME Wayland 桌面会话 ├── gsettings: 锁屏=off, 息屏=off │ ┌─── sunshine.service 启动 ───┐ │ ① ExecStartPre: pkill -9 sunshine; sleep 2 ← 清场 │ ② ExecStartPre: --creds <用户> '<密码>' ← 注入凭据 │ ③ ExecStart: flatpak run Sunshine ← 启动 │ ├── PipeWire 管道连接 (platform=wayland) │ ├── NVENC 编码器就绪 (encoder=nvenc) │ ├── force_tcp=true (纯 TCP 模式) │ └── 监听 47985,47989-48011 └──────────────────────────────┘ │ ┌─── rdp-tunnel.service 启动 ──┐ │ autossh -R 12个端口 → 服务器(SSH 密钥免密) └──────────────────────────────┘ │ → 服务器 localhost 端口就位 → ✅ 公司随时可连
第五部分:故障排查
症状:Moonlight 报 503 错误 503 = Sunshine 服务端无法初始化视频流。三种原因:
原因
日志特征
解决
凭据丢失
Open the Web UI to set your new username and password
systemd 中加了 ExecStartPre --creds,重启即可
端口冲突
Couldn't bind RTSP server to port [48011]
systemd 中加了 pkill -9; sleep 2,重启即可
视频初始化失败
failed to initialize video capture/encoding
检查 nvidia-smi、确认 platform = wayland、确认显示器电源开着
1 2 3 4 systemctl --user restart sunshine.service sleep 4systemctl --user status sunshine.service | head -3
症状:Sunshine 不监听端口 / 无限重启 1 2 tail -50 ~/.var/app/dev.lizardbyte.app.Sunshine/config/sunshine/sunshine.log
常见原因:
日志
解决
Couldn't bind RTSP server to port [48011], 地址已在使用
残留进程占端口 → pkill -9 sunshine; sleep 2; systemctl --user restart sunshine.service
libicuuc.so.70: cannot open shared object file
用了 deb 包不是 Flatpak → 卸载 deb,重装 Flatpak 版
NVENC not found
NVIDIA 驱动问题 → nvidia-smi 检查
症状:隧道无限重启 (activating auto-restart) 1 journalctl --user -u rdp-tunnel.service -n 20 --no-pager
日志
原因
解决
Permission denied (publickey,password)
SSH 密钥未配置
重做 1.0 的 ssh-copy-id
port forwarding failed
服务器上残留旧隧道
ssh ... "pkill -f 'sshd.*:479|sshd.*:48'"
症状:连上但黑屏 / 画面静止
家里显示器是否开着电源?(Wayland + PipeWire 需要显示器在活动状态)
确认编码器:grep encoder ~/.var/app/dev.lizardbyte.app.Sunshine/config/sunshine/sunshine.conf
调高码率:https://127.0.0.1:47991 → Configuration → Video → Bitrate: 15000-25000 kbps
症状:Moonlight 提示端口被阻止(47998/47999 等) 公司 SSH 隧道没开全。重新执行完整的 12 端口命令(见 3.3)。
附录:命令速查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 systemctl --user status sunshine.service systemctl --user restart sunshine.service journalctl --user -u sunshine.service -f tail -50 ~/.var/app/dev.lizardbyte.app.Sunshine/config/sunshine/sunshine.log systemctl --user status rdp-tunnel.service systemctl --user restart rdp-tunnel.service ss -tlnp | grep sunshine ssh -p <端口> <用户>@<IP> "ss -tlnp | grep 127.0.0.1 | grep 47" ssh -N -p <端口> \ -L 47989:127.0.0.1:47989 -L 47990:127.0.0.1:47990 \ -L 47991:127.0.0.1:47991 -L 47998:127.0.0.1:47998 \ -L 47999:127.0.0.1:47999 -L 48000:127.0.0.1:48000 \ -L 48002:127.0.0.1:48002 -L 48004:127.0.0.1:48004 \ -L 48006:127.0.0.1:48006 -L 48008:127.0.0.1:48008 \ -L 48010:127.0.0.1:48010 -L 48011:127.0.0.1:48011 \ <用户>@<IP> moonlight-qt & https://127.0.0.1:47991 ss -tlnp | grep 127.0.0.1 | grep 47 pkill -f 'sshd.*:47\|sshd.*:48' cat /var/log/tunnel-monitor.log
最后更新:2025-07-11 适用:Ubuntu 26.04 / GNOME Wayland / NVIDIA NVENC / Sunshine Flatpak 踩坑:NVIDIA + Wayland KMS 不兼容、systemd 竞态条件、sunshine_state.json 凭据耦合