Featured image of post 推荐一个网页打印项目cups-web,告别"远程开 Windows 发文件再打印”

推荐一个网页打印项目cups-web,告别"远程开 Windows 发文件再打印”

怎么把"远程给孩子打印作业"这件事,从每周 2-4 次的 2-5 分钟折腾,变成 30 秒搞定的网页操作。

最近也没有偷懒,一直没有整理笔记,主要是没找到更惊艳的工具或话题,生产互联网垃圾伤人伤己。( Claude Code 4.7 opus还在继续蹬 ,这篇内容是关于 NAS 打印相关的工具)

前提准备

外地 PC/手机、家庭 PVE 虚拟机已通过 WireGuard 组内网,可以正常互相访问;HP 打印机已正常联网。原来给孩子打作业都是通过 PVE 远程控制开机 Windows 虚拟机,把文件发过去再打印。 只有适合自己的方案才是最好的!!!

  • 家里 NAS,PVE+Ubuntu(docker 服务)+Windows(平时不开机,打印或者调试家里网络用)+NAS 服务

  • 打印机 HP LaserJet Pro MFP m26nw

那个让我有操作的瞬间

双职工家庭,工作日白天家里只有奶奶帮忙带娃。

每周总有那么 2-4 次,剧本几乎一模一样:低年级的孩子,老师会把每天的作业发到群里,一般口算题、写字之类的是需要打印的。

然后开始我的"远程打印仪式":

  1. 电脑连家里 WireGuard
  2. 进 PVE 上那台 为打印开着 的 Windows 虚拟机
  3. Windows 上登微信,下载这张图片(顺便忍受微信桌面版同步那几秒的转圈)
  4. 打开文件,Ctrl+P
  5. 孩子已经习惯了,看到老师作业内容,如果有需要打印,自己去打印机上面取 一顿操作,整套流程要 2-3 分钟,偶尔打印失败的时候,大概率是没纸了,我给老人或孩子打电话(他们都会加纸)。

这事让我难受的不是哪一步特别难,而是—— 它实在太有操作了

打印机自己带 WiFi,已经联网了;NAS(其实是 PVE 上的一台 Ubuntu 虚拟机)也 7×24 开着;我电脑网络畅通。但就因为缺一个"网页版打印入口",我不得不绕道一台 Windows 虚拟机,把一件物理上 3 秒就能完成的事拉长到几分钟。

直到我遇到了cups-web这个项目。

cups-web 解决了什么

一句话: 它把"打印"这件事变成了一个 HTTP 接口

往细了说,它做了三件事:

  • 用 CUPS 当打印内核(这是 Linux/macOS 几十年来通用的打印协议栈)

  • 套了一层现代化的 Web UI,浏览器拖文件就能打印

  • 后端自动把 Office、OFD、图片这些格式转成 PDF 再送进 CUPS

对我这个场景来说,最关键的是第二件事—— 有了 Web UI,远程访问就是水到渠成的 。配合 WireGuard 把家里网段打通,我在任何地方打开浏览器都能用。

它给我带来的真正改变

把架构画清楚:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[PC,外地]
       
       ▼  WireGuard
[家里路由器 / 内网 10.8.0.0/24]
       
       
[PVE Ubuntu VM]
  ├── cups-web (端口 1180,Web UI)
  └── cups     (端口 631,CUPS 服务)
       
       ▼  IPP over WiFi
[HP LaserJet Pro MFP M26nw]
       
       ▼  物理输出
[孩子到家取走作业]

cups-web 完全不暴露公网 ,所有访问统一走 WireGuard 隧道。安全模型 = 内网服务的安全模型,没有引入任何新的攻击面。

部署实战

开发者部署过程和常见问题写的比较清楚,推荐阅读:https://github.com/hanxi/cups-web

先说我的环境:

  • 宿主机:PVE 8.x

  • 虚拟机:Ubuntu 22.04 LTS,4 核 8G

  • VM 网络:桥接(bridge),和打印机在同一个 192.168.x.0/24 网段

  • Docker:docker-ce + compose v2

  • 打印机:HP LaserJet Pro MFP M26nw,WiFi 接入,路由器后台已绑定 MAC → 固定 IP

第一步:先确认 VM 能"看到"打印机

这一步是后面所有事的前提。在 Ubuntu VM 里:

1
2
3
4
5
6
7
8
# 看打印机是否在同一个广播域里
avahi-browse -art | grep -i hp

# 或者直接 ping 打印机 IP
ping -c 3 192.168.x.x

# 浏览器访问打印机 IP,看它的内置管理页(EWS)能不能打开
curl -I <http://192.168.x.x>

如果这三步有任何一步走不通,先别动 cups-web——回头查路由器的 VLAN/AP 隔离设置,或者 PVE 桥接网络的配置。 网络通了再继续 ,否则后面会浪费很多时间在错的地方排查。

第二步:写 docker-compose.yml

官方docker-compose.yml是为 USB 直通场景设计的,对网络打印机来说有两处冗余:devices: /dev/bus/usb不需要。 根据自己设备来操作。

我的最终版本,cups 服务采用 bridge 网络模式 ——这是最干净的方式,不污染 host 网络:

 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
services:
 cups:
   image: hanxi/cups:latest
   container_name: cups
   environment:
     - CUPSADMIN=${CUPSADMIN}
     - CUPSPASSWORD=${CUPSPASSWORD}
   ports:
     - "631:631"
   volumes:
     - ./.etc:/etc/cups
   restart: unless-stopped

 web:
   image: hanxi/cups-web:latest
   user: root
   container_name: cups-web
   environment:
     - CUPS_HOST=cups:631
   ports:
     - "1180:8080"
   volumes:
     - ./.data:/data
     - ./.uploads:/uploads
   depends_on:
     - cups
   restart: unless-stopped

几个关键点解释一下:

为什么 cups 用 bridge 而不是 host? 网上很多教程会让你用network_mode: host,理由是"让 CUPS 看到打印机的 mDNS 广播"。但对于网络打印机, 我们根本不依赖 mDNS 发现 ——后面添加打印机时直接填 IP 就行。bridge 模式让容器隔离更清晰,端口冲突风险更小。

容器间通信怎么走? CUPS_HOST=cups:631这一行——web 容器通过 Docker 内置的 DNS,用服务名cups直接访问 cups 容器的 631 端口。完全不需要走 host 网络。

第三步:起容器

在 docker-compose.yml 同级目录建一个.env:( 我首次登录也是用的开发者文档的默认密码,还是跟着作者来吧

1
2
CUPSADMIN=admin
CUPSPASSWORD=你自己的强密码

然后启动:

1
2
3
docker compose up -d
docker compose ps
docker compose logs -f cups

看到 cups 容器日志里Listening for connections on 0.0.0.0:631之类的提示就 OK 了。

第四步:在 631 端口把打印机加进 CUPS(这一步是关键差异)

浏览器打开http://<VM IP>:631,左上角 Administration → Add Printer ,用 .env 里的账号密码登录。

USB 教程到这里会让你在列表里选"USB Printer #1"之类的本地设备。 我们不一样

  1. Other Network Printers 区域,选 Internet Printing Protocol (ipp)
  2. Continue ,在 Connection 输入框里填: 把192.168.x.x换成 M26nw 的实际 IP。
1
ipp://192.168.x.x/ipp/print
  1. Name / Description / Location 随便起,比如HP_M26nw家里打印机客厅
  2. 重点:勾选 “Share This Printer” ——cups-web 通过 IPP 反向连接 CUPS 时,只能看到 shared 的打印机。这个勾不打,后面 cups-web 里就是空列表。
  3. Make 选 HP ,Model 选 IPP Everywhere (M26nw 支持 IPP Everywhere,几乎 driverless,比厂商专属驱动省心)
  4. Add Printer 加完之后在 Printers 标签页能看到它,状态是 Idle。打一份测试页(Maintenance → Print Test Page),打印机吐出一张 CUPS 自带的测试图就说明本地链路打通了。

第五步:访问 cups-web 实测

浏览器打开http://<VM IP>:1180,默认账号密码都是admin

登录后第一时间做两件事:

  1. 改密码(右上角用户菜单)
  2. 在打印机列表里应该能看到刚才那台HP_M26nw。如果没看到,回 631 检查 Share This Printer 有没有勾上 试着拖一张 PDF 进去,选份数,点 Print。打印机吐纸——本地链路就完成了。

部署实战 Part 2:让它"出门"

到这一步,cups-web 已经能在家里局域网内用了。但我的核心需求是 远程 ,所以需要 WireGuard 把家里网段打通到外面。

WireGuard 的服务端/客户端配置我之前写过一篇详细的:

NAS 内网穿透实战:用 WireGuard 在任何地方访问家里服务

上面这篇覆盖了配置文件、密钥生成、端口转发、DDNS 这些。这里就不重复了,只讲与打印场景特别相关的两个细节。

一段时间下来的副产品

用了几周之后,发现一些当初没预料到的好处。

PVE 上的 Windows 虚拟机直接基本退役了, 它原来存在的最大意义就是"打印代理"。

等待的焦虑没了 ,这是最实在的改善。

cups-web 自带打印记录有意外用处。 每条记录有时间、文件名、份数等,比如有时候上午语文老师发了,下午其他老师也需要打印,通过打印记录可以随时发现上午的材料有没有打印过。当然,这事见仁见智,介意隐私的可以把记录保留时间调短。

性能完全不是问题。 一张图片上传到打印开始吐纸大概 2-3 秒,Office 文档 LibreOffice 转 PDF 大概 5-8 秒。4 核 8G 的 VM 跑这个绰绰有余。

几个真踩过的坑

不是所有教程上的坑我都遇到了。下面这几个常见的挑出来说。

坑 1:M26nw 在 cups-web 里能选"双面打印",但它其实是单面机

M26nw 物理上不支持自动双面(要双面得手动翻面再送进去)。但 cups-web 的 UI 上"双面"选项是亮的,可以勾。

第一次手贱勾了,打印机吐出来的是单面,第二张纸又打了同一面内容,浪费了半张纸。

原因是 IPP Everywhere driverless 模式下,打印机能力上报有时候不准。cups-web 显示的是"理论上 CUPS 支持的所有选项",而不是"打印机真实支持的能力"。

解决方式 :习惯就好,永远默认单面。如果想彻底干掉这个误操作的可能,可以在 631 端口里给这台打印机改 PPD,把双面选项去掉——但我懒得搞,记住"单面机就是单面机"这一条就行了。

坑 2:路由器重启后打印机 IP 变了 → 一次性解决

这个其实算不上"坑",是部署之前就该做好的事: 第一次部署前,先在路由器后台给打印机的 MAC 地址绑定一个固定 IP

我用的小米路由器,路径是"设置 → 局域网 → DHCP 静态分配"。把 M26nw 的 MAC 填进去,分一个明确的 IP,从此再也没变过。

如果不做这一步,路由器重启或者打印机离线一段时间重连,DHCP 分配的 IP 可能变化,CUPS 里那条ipp://192.168.x.x/ipp/print就指向了空气。

cups-web 那台 VM 同理,一起绑了。

这些场景不推荐这套方案

写这种文章的人很多会避开"什么时候不该用",因为劝退看起来像在唱衰自己的方案。但我觉得 讲清楚边界才对得起读者

家里没人能配合"加纸 + 取件" 的,这套也用不起来。再优雅的远程打印,本质还是要有人物理上去做这两件事。

只用苹果设备,且只打 PDF/图片 的,AirPrint 已经够了,没必要折腾这一套。cups-web 的核心价值是"格式转换"和"远程统一入口",这两条对你不适用就划不来。

写在最后

cups-web 是hanxi维护的开源项目,MIT 协议。仓库地址:github.com/hanxi/cups-web

作者在 GitHub Issues 里非常活跃,README 里能看到一连串 issue 编号——大量驱动支持都是根据用户反馈陆续加上去的。作者也维护了微信群(Issue #36),开发节奏稳定,0.x 版本但功能已经相当完整。

📎 参考文章

Licensed under CC BY-NC-SA 4.0
最后更新于 May 19, 2026 08:14 CST