
简介这份 cups-2.3.3-source.tar.gz 是 CUPS通用 Unix 打印系统2.3.3 的完整源码包面向需要在 Linux、BSD、macOS 等类 Unix 环境中编译、定制打印服务的开发者与运维人员可用于搭建网络打印、研究 IPP 协议及裁剪驱动与后端。压缩包共 1294 个文件约 7.76MB以 512 个 tmpl 模板、201 个 c 源文件、81 个 html 页面、73 个 h 头文件为主并含 makefile、m4、po、test、drv、ppd 等构建、国际化、测试与驱动相关文件。源码涵盖 configure 构建脚本、src 服务器与过滤器实现、docs 文档手册、include 头文件、policies 默认打印策略、etc 配置示例以及 tests 测试用例可据此梳理打印作业从提交、格式转换到设备后端的处理链路理解 IPP、LPD 与 AirPrint 等协议支持方式。已有 928 人学习适合用来掌握 CUPS 的过滤器驱动模型、Web 管理界面和本地编译安装方法并作为定制打印方案或二次开发的参考底包目录组织完整便于按模块检索与二次开发对熟悉类 Unix 打印链路和运维排错较有帮助。1. 三台机器抢一台打印机问题往往不在打印机上工位上三台机器抢一台打印机是那种最难查的故障A 机器能打B 机器点了没反应C 机器打出来是乱码。驱动版本各不相同队列名也各不相同出事的时候没人说得清任务停在哪一环。cups-2.3.3-source.tar.gz 这类 CUPS 源码包就是为这种场面准备的它给的不是一个装完就能用的二进制而是一整套打印服务器的源码编译进什么、监听哪个端口、队列怎么起、日志开到多细全都由你决定。包管理器里的 cups 往往跟着发行版走版本、补丁、默认配置都是别人替你选的。当你要在老旧系统上补一个新特性要把打印能力塞进一个裁剪过的镜像或者要读 filter 源码搞清楚某个 PPD 为什么把 A4 打成了 Letter源码包就是唯一的路。这篇按我实际拆包的顺序写先编译、再配置、再拆任务链路、最后把踩过的坑一条条摆出来。适合手上正有一台不听话的打印服务器、又不想再靠重启碰运气的人。2. 编译前的环境盘点configure 参数与第一次 make 怎么不翻车CUPS 的构建系统是 autoconf 那一套configure 会把所有依赖探一遍缺什么当场报出来。麻烦的地方在于它探测的依赖很多是「有就用、没有就降级」不会报错等到运行期你才发现某个 filter 根本不存在。所以编译前先把依赖分成刚需和可选两类看清楚。2.1 依赖清单哪些缺了直接编不过哪些缺了只是少功能下表是我在一台干净的 Debian 系机器上装 CUPS 2.3.3 时的最小心集包名按 Debian/Ubuntu 写RHEL 系把-dev换成-devel即可。组件级别缺失后的表现常见包名zlib刚需configure 直接失败压缩打印数据无处解压zlib1g-devlibjpeg刚需JPEG 图像转换链路断裂图片任务报 filter failedlibjpeg-devlibpng刚需PNG 相关 filter 编不出来libpng-devlibtiff可选TIFF 类任务无法处理libtiff-devlibusb-1.0可选usb://后端不可用本地 USB 打印机扫不到libusb-1.0-0-devgnutls 或 openssl强烈建议无法启用 TLS加密队列和远程管理都受限libgnutls28-devavahi-client可选没有 DNS-SD 广播客户端自动发现失效libavahi-client-devdbus可选与桌面环境的会话集成能力下降libdbus-1-devpam可选无法用系统账户做 Web 管理认证libpam0g-dev装依赖这一步最容易犯的错是在一台只跑打印服务的机器上把整个桌面环境的开发包全装上结果 configure 探到一堆用不上的东西编出来的 cupsd 拖着一串运行时依赖。我的习惯是先只装 zlib、libjpeg、libpng、gnutls 四个编一遍看 configure 末尾的 summary 缺什么再补什么这样产出的二进制最干净。2.2 configure 参数逐项拆解每个开关都在改什么解包之后先别急着./configure把--help输出重定向到文件里过一遍不同分支的选项名会有出入。tar -xzf cups-2.3.3-source.tar.gz cd cups-2.3.3 ./configure \ --prefix/usr \ --sysconfdir/etc \ --localstatedir/var \ --with-docdir/usr/share/doc/cups \ --with-tlsgnutls \ --with-dnssdavahi \ --enable-debug \ 21 | tee /tmp/cups-configure.log几个参数值得单独说明。--prefix/usr决定二进制和库的落点CUPS 会把cupsd、lpadmin、lpstat放进$prefix/sbin和$prefix/bin把 backend 和 filter 放进$prefix/lib/cups/。如果你后面要跟发行版自带的 CUPS 共存这里就必须换一个 prefix否则两套程序会互相覆盖。--sysconfdir/etc让配置文件落在/etc/cups。这个值不要随便改很多第三方脚本硬编码了这个路径改了之后cupsctl和部分 PPD 工具会找不到配置。--localstatedir/var决定状态目录和 spool 目录的根任务队列文件最终落在/var/spool/cups日志落在/var/log/cups。--with-tlsgnutls指定 TLS 后端。CUPS 2.3 这条线支持 gnutls 和 openssl 两种选哪个看你系统里哪个库更顺手功能上没有明显差别。如果 configure 报「unrecognized option」说明这个分支没集成该项直接删掉这行重跑即可不要硬凑。--with-dnssdavahi打开 DNS-SD 广播局域网里的客户端才能自动扫到这台服务器。只在纯静态 IP、客户端全部手工填 URI 的环境里这一项可以关掉省一个运行时依赖。--enable-debug会保留调试符号并打开额外的断言排障阶段很有用但二进制会大不少。生产环境编第二遍时我会把它去掉。2.3 首次构建与安装make 之后先别急着启动make -j$(nproc) 21 | tee /tmp/cups-make.log make install # 校验配置文件语法这一步能在启动前拦掉大部分低级错误 /usr/sbin/cupsd -t # 确认版本与安装路径 /usr/bin/cups-config --version /usr/bin/cups-config --serverbinmake -j的并行数不要无脑拉到核数上限CUPS 里有几个生成 PPD 的大文件内存吃紧的机器上并行过高会把 OOM killer 招来编译中途被 kill 却只报一行莫名其妙的错误。4 到 8 之间比较稳。cupsd -t是启动前必跑的一步它只做配置解析不监听端口。配置写错时它会把行号和错误原因打出来比启动后去 error_log 里捞要快得多。cups-config则用来确认这套源码装出来的路径后面写启动脚本、配 systemd 单元时都要拿它输出的--serverbin和--serverroot对齐。安装完成后不要立刻systemctl start cups。发行版自带的 cups 服务很可能已经在跑源码版再起一个会撞端口。先ss -lntp | grep 631看一眼有监听就先停掉旧服务或者干脆给新实例换一个端口做并行验证。3. 配置落地cupsd.conf 的访问控制与队列建立的两条路径CUPS 的配置被拆成了几个文件cupsd.conf管服务行为和访问控制cups-files.conf管路径、用户、组和日志位置printers.conf是队列的运行时状态不要手改。很多人改完配置重启没反应八成是改错了文件。3.1 cupsd.confListen、Location 与访问控制的三层结构# /etc/cups/cupsd.conf LogLevel warn MaxLogSize 2m Listen 0.0.0.0:631 Listen /run/cups/cups.sock WebInterface Yes DefaultEncryption IfRequested DefaultAuthType Basic MaxJobs 500 PreserveJobHistory Yes PreserveJobFiles No Location / Order allow,deny Allow LOCAL /Location Location /admin AuthType Default Require user SYSTEM Order allow,deny Allow LOCAL /Location Location /printers Order allow,deny Allow LOCAL /LocationListen 0.0.0.0:631让服务监听所有网卡。只给内网用时直接写内网地址更安全。Listen /run/cups/cups.sock是本地域套接字本机客户端走它比走 TCP 快也不受防火墙影响。Location /里的Allow LOCAL表示只允许同网段访问。这个宏在 2.3 这条线上是按网卡地址和掩码自动算的多网卡机器上偶尔会出现「明明同网段却 403」的情况这时把它换成具体的Allow 192.0.2.0/24更可靠。Location /admin里的Require user SYSTEM依赖cups-files.conf中的SystemGroup两处必须对得上否则 Web 管理页会一直弹密码框。PreserveJobFiles No是省磁盘的关键。它关掉之后任务打完就把 spool 里的数据文件删掉只留历史记录。开着的话一个卡住的大任务能把/var/spool/cups撑满然后整个服务对所有任务返回失败这个现象特别容易误判成打印机坏了。路径和权限相关的部分在另一个文件里# /etc/cups/cups-files.conf User lp Group lp SystemGroup lpadmin ServerRoot /etc/cups ServerBin /usr/lib/cups StateDir /var/run/cups RequestRoot /var/spool/cups TempDir /var/spool/cups/tmp CacheDir /var/cache/cups AccessLog /var/log/cups/access_log ErrorLog /var/log/cups/error_log PageLog /var/log/cups/page_logSystemGroup lpadmin这一行决定了谁能进管理页、谁能执行lpadmin。把运维账号加进这个组之后必须重新登录才生效newgrp也行但在脚本里跑的时候经常忘于是出现「手动能建队列、定时脚本建不了」的玄学现象。3.2 队列怎么建PPD 模式和 IPP Everywhere 模式CUPS 2.3 建队列有两条路。第一条是传统的 PPD 驱动模式队列绑定一个描述文件所有页面设置、纸张来源、双面能力都写在 PPD 里。第二条是 IPP Everywhere不装驱动让 CUPS 直接向打印机要能力集自动生成临时 PPD。先看设备发现这一步决定你用哪种 URI# 列出当前能发现的所有设备 URI lpinfo -v # 列出可用的驱动/PPD lpinfo -m | grep -i genericlpinfo -v会输出network ipp、network socket、direct usb这几类。如果打印机支持 IPP优先选ipp://开头的 URI能力协商和状态回传都走标准协议比裸 socket 好排错。IPP Everywhere 建队列lpadmin -p Floor3-Mono -E \ -v ipp://printer-floor3.example.internal/ipp/print \ -m everywhere \ -D 3F 黑白激光 \ -L 3F-East \ -o printer-is-sharedtrue-p是队列名建议只用字母数字和连字符带空格或中文的名字在某些老客户端上会被截断。-E表示启用队列并接受任务少了它会建出一个暂停状态的队列任务全堆在那儿不动。-m everywhere触发能力协商CUPS 会向打印机发Get-Printer-Attributes拿回来的属性生成临时 PPD 放在/etc/cups/ppd/下。-D是描述-L是位置这两个字段在几十个队列的时候是救命的客户端下拉框里能直接看出是哪台。PPD 模式建队列lpadmin -p Meeting-Room -E \ -v socket://192.0.2.10:9100 \ -m generic.ppd \ -o PageSizeA4 \ -o DuplexDuplexNoTumblesocket://是最原始的方式把打印数据裸推到 9100 端口。它没有任何状态回传打印机缺纸、卡纸、脱机CUPS 一概不知道任务照样显示完成。只有在打印机不支持 IPP、又不想配 LPD 的时候才用它。-o后面的选项名必须和 PPD 里定义的一致拼错不会报错只会静默失效所以建完队列一定要lpstat -l回看实际生效的选项。队列多的时候手工敲不现实用脚本生成#!/usr/bin/env python3 # 从 CSV 批量建队列格式name,uri,driver,location import csv, subprocess, sys def build(row): name, uri, driver, loc row[name], row[uri], row[driver], row[location] cmd [lpadmin, -p, name, -E, -v, uri, -m, driver, -L, loc] # -m everywhere 时不传 PPD 文件名其余情况按驱动名走 print( .join(cmd)) subprocess.run(cmd, checkTrue) if __name__ __main__: with open(sys.argv[1], newline, encodingutf-8) as f: for row in csv.DictReader(f): build(row)这个脚本只做一件事把建队列的动作变成幂等的可重放操作。注意lpadmin -p对已存在的队列是修改而不是新建所以同一份 CSV 重跑不会报错这个特性在批量修 URI 的时候很好用。subprocess.run(checkTrue)让任意一条失败就中止避免批量建到一半留下一堆半成品队列。3.3 验证队列把「建完了」和「真能用」分开lpstat -t lpstat -l -p Floor3-Mono lpstat -W not-completed -o # 打一张测试页 lp -d Floor3-Mono /etc/hostnamelpstat -t一次打出调度器状态、所有队列、所有未完成任务是最快的体检命令。lpstat -l -p会额外显示队列绑定的 PPD 路径和生效选项用来确认-m everywhere是不是真的生成了临时 PPD。第三行只列未完成任务卡住的任务在这里一眼能看到。用/etc/hostname当测试文件是个小技巧纯文本、几百字节、任何驱动都能处理。如果它打得出来而 PDF 打不出来问题一定在 filter 链而不是网络。4. 打印链路拆解任务从 lp 到出纸究竟过了哪些进程排障卡壳的根本原因是大多数人不知道 CUPS 内部有几个环节。把它拆成六段之后每一段都有对应的日志和验证命令就不需要靠重启来碰运气了。4.1 一次打印任务的六个环节第一段是应用侧程序把文档交给 libcups 或直接发 IPP 请求。第二段是 cupsd 的调度器它收到请求后分配一个任务号把控制文件写成c000xx、数据文件写成d000xx落在/var/spool/cups。第三段是 filter 链按 MIME 类型转换比如 PDF 转成打印机认识的栅格格式。第四段是 backend负责把数据推到设备ipp、socket、usb、lpd都是这一层的实现。第五段是设备回传状态。第六段是任务收尾写 page_log、清理 spool。对应到目录环节位置出问题时的典型现象调度器cupsd 进程日志在 error_log任务停在「已提交」不动任务 spool/var/spool/cups磁盘满所有任务失败filter/usr/lib/cups/filter状态显示 Filter failedbackend/usr/lib/cups/backend状态显示 Unable to write print dataPPD/etc/cups/ppd页面尺寸、双面设置不对设备状态ipp 回传或没有回传显示完成但没出纸filter 和 backend 的报错是最容易被误读的。Filter failed不代表打印机有问题它只说明某个转换程序退出码非零原因可能是缺库、可能是权限、也可能是输入文件本身损坏。判断方法是在 error_log 里找到具体的命令行手动以 lp 用户重跑一遍。4.2 日志分级与定位命令CUPS 的日志有三个access_log记 HTTP 请求error_log记服务与任务page_log记每次实际打印的页数。排任务问题只看 error_log 就够了。# 临时把日志级别压到 debug cupsctl --debug-logging # 盯住某个任务的全部日志 tail -F /var/log/cups/error_log | grep -E \[Job [0-9]\] # 看某个时间段的 backend 调用 grep -E backend|filter /var/log/cups/error_log | tail -50cupsctl --debug-logging等价于把LogLevel改成 debug重启后生效排完记得改回 warn不然日志一天能涨几百兆。[Job N]这个前缀是 CUPS 给任务日志打的标记用它 grep 能把一个任务从接收到结束的所有行串起来比按时间翻日志高效得多。事情出在打印机侧、CUPS 自己又说不清的时候直接对打印机发 IPP 请求ipptool -tv ipp://printer-floor3.example.internal/ipp/print \ /usr/share/cups/ipptool/get-printer-attributes.testipptool是 CUPS 自带的 IPP 客户端源码包的examples/目录里有几份.test模板安装后一般在/usr/share/cups/ipptool/下。-t打印传输细节-v打印属性值。这条命令能拿到打印机的真实状态、支持的操作、耗材余量绕过 CUPS 直接判断是设备问题还是服务问题。4.3 客户端对接选型IPP、LPD、Socket 该怎么选协议URI 形式状态回传适用场景IPPipp://host/ipp/print完整新设备首选能力协商、耗材状态都有IPPSipps://host:631/ipp/print完整跨网段、需要加密Socketsocket://host:9100无老设备兜底只保证数据能推过去LPDlpd://host/queue极弱只有 LPD 的老打印机USBusb://Vendor/Model有限本机直连Windows 客户端通过共享连接时走的是 SMB 或 IPP这时候要在cupsd.conf里确认WebInterface和共享开关都开着并且SystemGroup里那个组对队列有访问权限。跨网段共享最常见的翻车是只改了Listen没改Location /的 Allow 规则服务在监听但所有远程请求被 403 挡掉。5. 避坑与排查五条踩坑记录和对应的定位动作这一节是按「现象 → 原因 → 解决」整理的。每一条都是我或者同事实际撞过的写下来是因为它们都不报明显错误只表现为「打不出来」。5.1 五条踩坑记录第一条源码版启动失败日志里只有一行 Address already in use。现象是cupsd前台启动立刻退出error_log 里翻来覆去就那一行。原因是发行版自带的 cups 服务在 631 端口上跑着两套程序抢同一个端口。解决方法是先ss -lntp | grep 631找到占用进程systemctl stop cups停掉旧服务再决定是让 systemd 指向新二进制还是保留两套并行。并行的话记得给源码版换一个Port并且客户端 URI 里的端口也要跟着改。第二条Web 管理页输对密码也进不去一直弹框。原因是SystemGroup指定的组里没有当前用户或者改了组之后没有重新登录进程还带着旧的组列表。另一种情况是Require user SYSTEM写在了Location /admin里但cups-files.conf中SystemGroup被注释掉了这时只有 root 能进。解决方法是id确认当前用户的组getent group lpadmin确认组成员改完之后重新登录再试。用 curl 验证更干脆curl -s -u user:pass http://127.0.0.1:631/admin/返回 200 就说明认证链路通了问题在浏览器缓存。第三条任务状态停在「Processing」最后变成 Filter failed。原因是 filter 链里某个转换程序退出码非零。常见触发点是缺 libjpeg 或 libpng 的运行时库、PPD 与驱动不匹配、或者输入 PDF 本身损坏。解决方法是去 error_log 里搜这个任务号找到完整的 filter 命令行然后以 lp 用户手动跑一遍sudo -u lp /usr/lib/cups/filter/pdftoraster 1 user job 1 PageSizeA4 /tmp/in.pdf /tmp/out.raster echo $?退出码能直接告诉你卡在哪。如果手动跑能过、走 CUPS 就失败那大概率是 AppArmor 或 SELinux 拦了 filter 对临时目录的访问看dmesg里有没有 denied 记录。第四条小文件能打大文件打到一半停住打印机面板没有任何报错。这种是典型的传输层半开。socket://后端没有超时协商防火墙或交换机在中途把空闲连接掐了CUPS 那边还以为在正常写。表现就是打了十几页然后永远卡住取消也取消不掉。解决方法是换ipp://它有请求响应机制断了能感知到实在只能用 socket 的把防火墙的连接跟踪超时调长或者在cupsd.conf里加Timeout控制单次读写等待。这个坑我用血泪经验换来的结论是能不选 socket 就别选。第五条改完 cupsd.conf 重启行为一点变化都没有。原因是 CUPS 的配置分在两个文件里改了cupsd.conf却把日志路径写在了cups-files.conf或者反过来。还有一种更隐蔽的情况systemd 单元的ExecStart带了-c指向另一个配置文件你怎么改/etc/cups/cupsd.conf都不生效。解决方法是ps -ef | grep cupsd看进程实际带的参数cupsd -t -c /path/to/conf单独校验你正在改的那个文件两边对上了再重启。5.2 日志、备份与回滚三件套排障阶段最值钱的是「能回到上一个已知可用状态」。CUPS 的配置改动很容易连锁改一个Allow规则可能让整个网段的客户端全部掉线。我的做法是每次动配置之前先打包TS$(date %Y%m%d-%H%M%S) tar -czf /root/cups-backup-$TS.tar.gz \ /etc/cups/cupsd.conf \ /etc/cups/cups-files.conf \ /etc/cups/ppd/ 2/dev/null # 回滚 tar -xzf /root/cups-backup-$TS.tar.gz -C / cupsd -t systemctl restart cups注意备份里不要包含printers.conf和/var/spool/cups前者是运行时状态直接还原容易和当前进程状态打架后者可能是几个 G 的残留任务。真正需要版本化的只有两个 conf 和ppd/目录。日志侧的习惯是给MaxLogSize设一个上限并且把error_log单独挂一个小分区或者用 logrotate 管起来。CUPS 在 debug 级别下的日志增长速度非常夸张一个卡住的任务循环重试几小时就能把根分区写满然后整个系统一起遭殃。6. 进阶把一整套 CUPS 配置变成可复制模板配好一台服务器只是开始真正麻烦的是第二台、第三台。我的做法是把配置拆成「要版本化的」和「运行时生成的」两类前者进仓库后者永远由脚本重建。需要进仓库的只有四样cupsd.conf、cups-files.conf、一个描述队列的 CSV、以及自定义的 PPD 文件。printers.conf不进仓库因为它记录的是内部状态号还原到另一台机器上毫无意义队列永远用第 3 章那个脚本从 CSV 重建。模板化之后部署流程收敛成三步# 1. 铺配置 install -m 644 repo/cups/cupsd.conf /etc/cups/cupsd.conf install -m 644 repo/cups/cups-files.conf /etc/cups/cups-files.conf # 2. 校验 重建队列 cupsd -t systemctl restart cups python3 repo/cups/build_queues.py repo/cups/queues.csv # 3. 与基线比对确认没有漂移 lpstat -l -p /tmp/queues-now.txt diff /tmp/queues-now.txt repo/cups/queues-baseline.txt echo 无漂移第三步是我最看重的一步。lpstat -l -p的输出里包含队列名、URI、PPD 路径、生效选项和状态把它当基线存下来之后任何一次手工改动都会在 diff 里暴露。团队里有人临时给某个队列加了个PageSizeLetter又忘了改回去第二天 diff 就会指出来。这个习惯比任何文档都管用。还有一个容易被忽略的细节-m everywhere生成的临时 PPD 存在/etc/cups/ppd/下文件名是队列名加.ppd。它会在打印机能力变化或队列重建时被覆盖所以不要把自定义选项直接改在这个文件里改了下一次重建就没了。要固化选项用lpadmin -p name -o 选项值写进队列配置再让它进基线。一个具体技巧把队列的 CSV 和基线文件放在同一个提交里每个 commit 都能回答「这次动了哪些队列」。新机器上克隆仓库、跑三个步骤、diff 通过就算部署完成全程不用打开浏览器。升级 CUPS 版本时也一样新二进制起来之后先跑 diff确认队列能力和选项没被新版改掉再切流量。从那以后我每次改打印服务器都强制走一遍cupsd -t、备份、重建、diff 这四步哪怕只是改一行 Allow 规则。看起来啰嗦但它把「改完不知道哪里又坏了」这种最耗时间的排查变成了一个几秒钟就能给出答案的 diff。希望帮到你。本文还有配套的精品资源点击获取