Homelab 搭建手记(8)通过 Apache Guacamole 访问 Debian 桌面
创建于 2026-09-05
更新于 2026-09-09
科技
Docker
Homelab
Debian
XRDP
Caddy
XFCE
Guacamole
6688 字 · 约 23 分钟

前言

Homelab 搭建手记(6)Debian Xfce 与 XRDP 同用户并发会话配置 中,我已经把 DebianXFCEXRDP 的远程桌面环境调整到可以长期使用;上一篇又通过 Homelab 搭建手记(7)部署 code-server 并配置 HTTPS 访问 把浏览器开发环境接了出来。

不过 code-server 只能提供编辑器和终端,遇到必须使用完整图形界面的程序时,还是需要打开原生 RDP 客户端。这次增加一个浏览器入口:使用 Apache Guacamole 作为 Web Gateway,通过 RDP 连接已有的 xrdp 会话。这样既保留原生 RDP,也可以在平板、轻薄本或临时设备上直接用浏览器进入 Debian 桌面。

配套脚本放在我维护的 homelab-setup 项目中。这个仓库用于初始化 Debian 环境和部署常用服务;这次加入 Guacamole 时,我也整理了有状态服务的目录管理,让生成的配置有固定位置,重新执行脚本时保留已有账号和连接数据。下面会同时说明脚本用法和具体配置。

一、目标架构

Guacamole 连接的是现有的远程 XFCE 会话,访问链路如下:

text
1
2
3
4
5
6
7
8
9
10
11
12
13
浏览器 │ HTTPS ▼ Caddy │ 反向代理 ▼ Apache Guacamole │ RDP ▼ xrdp │ ▼ 已有的 XFCE 远程会话

xrdp 是远程桌面后端,Guacamole 把浏览器连接转换为 RDP,Caddy 负责 HTTPS 和外部入口。原生 RDP 客户端仍然可以直接连接 xrdp,两种访问方式互不替代。

二、为什么选择 Guacamole

如果只是想在浏览器中显示一个 Linux 桌面,也可以选择专门的 Web Desktop 容器。但那通常意味着重新创建用户、桌面环境和数据目录,和当前已经调通的 xrdp 会话变成两套互不相干的系统。

Guacamole 更适合当前场景,因为它只负责把浏览器连接转发为 RDP,不需要安装另一套桌面。这样可以保留之前已经验证过的本地与远程会话共存、同用户会话复用,以及输入法、D-Bus 和密钥环等配置。Guacamole 自身通过 Docker 隔离,也不会向宿主机额外安装 Tomcat、Java 或 PostgreSQL 运行时。

三、为什么把有状态服务统一放在 services/docker/

过去部署 Docker 服务时,Compose 文件有时放在 /opt,有时放在用户目录。服务多了,就不容易记住配置、环境变量和初始化 SQL 分别放在哪里,备份时也容易漏掉。因此这次把生成的服务配置统一放到 services/docker/ 下,每个服务使用自己的子目录。

因此我在 homelab-setup 中把服务入口和数据工作区分开:setup/ 保存可公开的安装与生成脚本,工作区中的 services/docker/ 保存本机服务配置;镜像和 PostgreSQL volume 仍由 Docker 管理,不会被错误地当成 Git 内容提交。

默认目录关系如下:

text
1
2
3
4
5
6
7
8
~/workspace/ ├── setup/ # 公开的 homelab-setup 仓库 └── services/ └── docker/ └── guacamole/ # 本机有状态服务配置 ├── compose.yaml ├── .env # 本机凭据,权限 600,不提交 └── initdb.sql

数据工作区默认是 $HOME/workspace,需要换到数据盘时可使用 --workspace-root 统一指定,省去逐个修改服务路径的操作。备份时要保存这里的 Compose 文件和 .env,并单独备份 Docker volume 中的数据。

Compose 文件本身占用很小空间,PostgreSQL 数据和镜像层实际位于 Docker 默认数据目录。部署前可以检查:

bash
1
2
sudo du -sh /var/lib/docker 2>/dev/null docker system df

四、通过 homelab-setup 部署并复用服务状态

我为 homelab-setup 增加了 services/docker/00-guacamole.sh。它先检查 Docker 与 Docker Compose,再在上述目录中生成缺失的部署文件、校验 Compose 配置并启动容器。重复执行时不会覆盖已有 .envinitdb.sql 或数据库卷;默认只拉取本地缺少的固定版本镜像,只有显式使用 --upgrade 才会拉取更新。

克隆公开仓库后,在仓库目录执行:

bash
1
2
3
4
5
6
7
8
git clone https://github.com/DoraTiger/homelab-setup.git "$HOME/workspace/setup" cd "$HOME/workspace/setup" # 按编号选择 Guacamole 服务 bash service.sh --silent docker 00 # 数据工作区位于其他磁盘时 # bash service.sh --silent --workspace-root /data/homelab docker 00

执行完成后,日常管理回到生成的服务目录:

bash
1
2
3
4
cd "$HOME/workspace/services/docker/guacamole" docker compose ps docker compose logs -f docker compose restart

如果不使用脚本,下面就是它所实现的关键原理。Guacamole 的 PostgreSQL 表结构由官方镜像生成,并固定版本以避免 latest 标签在升级时悄悄改变行为:

bash
1
2
3
4
docker run --rm guacamole/guacamole:1.6.0 \ /opt/guacamole/bin/initdb.sh --postgresql > initdb.sql ls -lh initdb.sql head initdb.sql

脚本会自动生成随机数据库密码并以 600 权限保存到部署目录的 .env。数据库密码只放在本机的 .env 中,不写入文章、Compose 文件或 Git 仓库:

bash
1
chmod 600 "$HOME/workspace/services/docker/guacamole/.env"
dotenv
1
GUACAMOLE_DB_PASSWORD=替换为随机生成的密码

五、编排 Guacamole、guacd 与 PostgreSQL

compose.yaml 由三个容器组成:postgres 保存用户和连接配置,guacd 负责协议代理,guacamole 提供 Web 界面。

yaml
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
services: postgres: image: postgres:17 restart: unless-stopped environment: POSTGRES_DB: guacamole_db POSTGRES_USER: guacamole_user POSTGRES_PASSWORD: ${GUACAMOLE_DB_PASSWORD} volumes: - postgres-data:/var/lib/postgresql/data - ./initdb.sql:/docker-entrypoint-initdb.d/001-guacamole.sql:ro networks: [guacamole] guacd: image: guacamole/guacd:1.6.0 restart: unless-stopped networks: [guacamole] guacamole: image: guacamole/guacamole:1.6.0 restart: unless-stopped depends_on: [postgres, guacd] environment: GUACD_HOSTNAME: guacd POSTGRESQL_ENABLED: "true" POSTGRESQL_HOSTNAME: postgres POSTGRESQL_DATABASE: guacamole_db POSTGRESQL_USERNAME: guacamole_user POSTGRESQL_PASSWORD: ${GUACAMOLE_DB_PASSWORD} WEBAPP_CONTEXT: ROOT ports: - "127.0.0.1:30090:8080" networks: [guacamole] networks: guacamole: volumes: postgres-data:

setup 模板设置了 WEBAPP_CONTEXT: ROOT,因此 Guacamole 直接位于 /。端口映射使用 127.0.0.1:30090:8080,宿主机上的 Web 端口只监听回环地址,浏览器通过 Caddy 访问。

30090 延续了 homelab-setup 使用高位端口的约定,前文的 code-server 对应 30080。这样可以减少与常见低位端口的冲突,也便于区分反向代理的目标服务。高位端口本身不提供安全保护,仍需保留回环地址绑定和 HTTPS 配置。手动编写 Compose 文件时,也应与脚本生成的路径和端口保持一致。

启动并检查容器:

bash
1
2
3
4
5
cd "$HOME/workspace/services/docker/guacamole" docker compose up -d docker compose ps curl -I http://127.0.0.1:30090 docker compose logs --tail=100

六、通过 Caddy 提供 HTTPS

浏览器远程桌面和 code-server 一样,需要一个稳定的 HTTPS 域名。实际部署时将示例域名替换成自己的域名:

本文沿用前文的证书签发方式:由 Caddy 通过阿里云 DNS-01 完成域名验证和 SSL 证书签发,alidns 插件读取阿里云 DNS API 凭据。也就是说,下面的配置适用于当前使用阿里云 DNS 的环境;如果改用 Cloudflare、腾讯云或其他 DNS 服务商,需要将 alidns 插件和对应的凭据配置一并替换,不能只修改域名。

caddy
1
2
3
4
5
6
7
8
9
10
desktop.example.com { tls { dns alidns { access_key_id {env.ALIYUN_ACCESS_KEY_ID} access_key_secret {env.ALIYUN_ACCESS_KEY_SECRET} } } reverse_proxy 127.0.0.1:30090 }
bash
1
2
sudo caddy validate --config /etc/caddy/Caddyfile sudo systemctl reload caddy

Caddy 原生支持 WebSocket 反向代理,不需要手动添加 UpgradeConnection 请求头。访问 https://desktop.example.com/ 后,应该能看到 Guacamole 登录页。

七、首次登录与 RDP 连接

初始化数据库后,Guacamole 的初始管理员账号是 guacadmin,初始密码也是 guacadmin。首次登录后,应立即新建一个自己的管理员账号并授予全部管理权限;退出后用新账号重新登录,确认权限和连接配置都正常,再删除默认的 guacadmin 账号。管理密码、RDP 密码和 DNS API 凭据不要复用,也不要把默认账号留在长期运行的实例中。

Settings → Connections → New Connection 中创建连接:

text
1
2
Name: Debian XFCE Protocol: RDP

连接由 guacd 容器发起,因此不能填写 127.0.0.1。对 guacd 来说,这个地址指向的是容器自身,而不是 Debian 宿主机。这里应该填写 Debian 在局域网中的地址:

text
1
2
Hostname: YOUR_DEBIAN_LAN_IP Port: 3389

第一次连接可以使用 Security mode: Any,并临时启用 Ignore server certificate,以兼容 xrdp 默认自签名证书。长期运行时应结合实际证书和网络边界重新评估。

八、最容易踩的坑:RDP 不要误选成 VNC

这次排障中最有代表性的问题,是浏览器能够打开 Guacamole,点击连接后却一直停留在“已连接,等待应答”。guacd 日志显示它实际创建的是:

text
1
Creating new client for protocol "vnc"

而目标端口是 xrdp 的 3389,链路就变成了:

text
1
Guacamole ── VNC ──→ Debian:3389 ──→ xrdp

协议不匹配时,xrdp 日志会出现 X.224 握手失败。这不是 xrdp 本身损坏,而是连接类型选错。重新把连接类型改为 RDP,并确认日志出现:

text
1
Creating new client for protocol "rdp"

这是判断协议是否正确的最快方法。

九、确认 WebSocket 和会话复用

连接建立后,可以在浏览器开发者工具的 Network 面板中搜索 websockettunnel,确认 /websocket-tunnel 返回 101 Switching Protocols。这说明浏览器、Caddy 和 Guacamole 之间的 WebSocket 链路正常。

正确使用 RDP 后,Guacamole 可以复用此前已经存在的远程 XFCE 会话。这次部署没有修改已经稳定的 /etc/xrdp/startwm.sh,也没有额外创建一套桌面环境。

十、显示、动态分辨率与剪贴板

这个浏览器入口主要用于 ChatGPT、Obsidian、浏览器和终端,连接参数应优先保证文字可读性和窗口适配:

text
1
2
3
4
5
6
7
8
9
10
11
Resize method: Display update Color depth: 32-bit DPI: 96 Clipboard: Bidirectional Font smoothing: Enabled Theming: Enabled Wallpaper: Disabled Animations: Disabled Full-window drag: Disabled Drive redirection: Disabled unless needed Audio: Enable only if needed

Display update 会在浏览器窗口大小变化时请求远程桌面调整分辨率。Guacamole 使用 Canvas 显示远程画面,清晰度不一定完全等同于原生 RDP 客户端;浏览器缩放建议保持 100%,再根据实际显示器测试 DPI。

十一、最终访问体系

text
1
2
3
4
5
原生 RDP 客户端 ─────────────→ xrdp ──→ XFCE 浏览器 ─→ HTTPS ─→ Caddy ─→ Guacamole ─→ RDP ─→ xrdp ──→ XFCE 浏览器 ─→ HTTPS ─→ Caddy ─→ code-server

日常长时间使用时,我还是更倾向于原生 RDP,画质和延迟表现更好;临时设备、平板或无法安装客户端时,再通过 Guacamole 连接。

十二、维护与安全边界

bash
1
2
3
4
5
6
7
8
cd "$HOME/workspace/services/docker/guacamole" docker compose ps docker compose logs --tail=100 docker compose logs -f guacd docker compose restart docker compose down docker compose up -d docker system df

长期运行时需要注意:PostgreSQL volume 保存用户、连接、权限和连接参数,必须纳入备份;.env 权限应保持为 60030090 继续只监听 127.0.0.1;真实客户端 IP 经过 Caddy 和 Docker bridge 后可能被代理地址替代,审计和暴力破解防护需要结合实际代理链路验证。

参考

手机扫码阅读
本文作者: 有次元袋的 tiger
本文链接: https://www.superheaoz.top/2026/09/51763/
版权声明: 本站点所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 我的个人天地