CUPS 2.3.3源码编译安装与cupsd打印排错

发布时间:2026/10/23 17:35:24
CUPS 2.3.3源码编译安装与cupsd打印排错

简介:这份 CUPS 2.3.3 源码包面向 Linux、BSD、macOS 等类 Unix 系统的开发者与运维人员,用于在本地编译、定制和部署开源打印系统,解决网络打印作业管理、驱动过滤与打印服务配置等实际问题。包内共 1294 个文件,以模板文件、C 源码、HTML 文档、头文件及测试脚本为主,另有构建配置、手册页、策略示例和图片资源,压缩包约 7.76MB;源码目录涵盖服务端、库、过滤器、文档、策略与测试等模块,结构完整,便于按模块查阅和二次开发。已有 928 人学习关注。借助这套源码,读者可对照 configure 与自动化构建体系完成环境适配,理解 IPP 打印作业处理、过滤器驱动模型、Web 管理接口与 SSL/TLS 传输安全等实现思路,并基于示例策略和测试用例开展本地验证,适合需要深入掌握 CUPS 架构、定制打印方案或参与社区贡献的中高级技术人员。

1. 从一台 cupsd 起来了但纸出不来的机器说起

某公司内网有一台打印服务器,发行版自带的那套打印组件版本偏旧,接某台网络打印机时队列能建、作业能提交,纸就是不出来。折腾半天决定上源码,锁 CUPS 2.3.3。CUPS 2.3.3 源码安装这件事,麻烦的从来不是 make 那几分钟,而是 configure 时少装了一个开发包、cups-files.conf 里 User 写错、装完发现和系统自带的老 cupsd 抢 631 端口。这类问题编译期一声不吭,全在运行期以「队列停住」「Forbidden」「Filter failed」的形式冒出来。下面这份记录从依赖清单一路写到一张纸真的从机器里出来,第一次源码编 CUPS 的人可以照着走,手里已经有 2.3.3 包、只想核对参数的人也能直接翻到 configure 那节对一遍。

2. 装前把依赖对齐:CUPS 2.3.3 编译要过哪几道关

2.1 为什么很多环境还锁在 2.3.3

CUPS 2.3.3 属于 2.3 系列,构建方式还是 autotools 那一套:./configure探一遍环境,make编译,make install落地。这个系列在配置文件的划分上已经定型——cupsd.conf管监听、访问控制和日志级别,cups-files.conf管运行用户、目录、日志文件路径。网上流传的很多老教程还停留在「把 User 写进 cupsd.conf」的年代,照着抄到 2.3.3 上,cupsd 重启后直接起不来,日志里只丢一句配置文件解析失败,这是最常见的版本坑。

选源码而不是直接用系统包,通常有三个理由:系统仓库里的版本太老,缺 IPP Everywhere 或者缺 avahi 支持;需要自己指定 TLS 后端,让证书链路和现有环境对齐;需要把 CUPS 装到/usr下,和系统里其它依赖 libcups 的组件共用一套库。三种理由里任何一种成立,源码编译就是唯一路径。反过来,如果只是想要一个能打本地 USB 打印机的环境,系统包通常更省事,不必折腾编译。

还要提醒一句:2.3.3 是一个源码包,不是二进制包,tar.gz 解出来是完整源码树,没有预编译产物,也没有安装脚本,所有路径和开关都得自己在 configure 阶段定下来。

2.2 依赖清单:分 apt 系和 dnf 系列一遍

CUPS 的依赖分两类:必需的和「有就编进去、没有就静默跳过」的。后者是翻车重灾区,configure 不会因为缺 avahi 就停下,它只是把 Bonjour 发现功能关掉,你以为装好了,实际网络打印机在客户端列表里根本看不见。所以依赖要一次性列全。

组件作用apt 系包名dnf/yum 系包名
gcc / make编译器和构建工具build-essentialgcc gcc-c++ make
zlibPDF 与位图数据压缩zlib1g-devzlib-devel
libpngPNG 图像 filterlibpng-devlibpng-devel
libjpegJPEG 图像 filterlibjpeg-devlibjpeg-turbo-devel
libtiffTIFF 图像 filterlibtiff-devlibtiff-devel
freetype / fontconfig字体渲染,文字类 filter 依赖libfreetype6-dev libfontconfig1-devfreetype-devel fontconfig-devel
libusb-1.0USB backend,本地 USB 打印机必需libusb-1.0-0-devlibusb1-devel
avahi-clientBonjour / DNS-SD 设备发现libavahi-client-devavahi-devel
gnutls 或 opensslTLS,加密 IPP 连接libgnutls28-dev 或 libssl-devgnutls-devel 或 openssl-devel
PAM管理认证libpam0g-devpam-devel
libpaper默认纸张尺寸libpaper-devlibpaper-devel
gettext本地化消息gettextgettext

安装命令按包管理器分两条,先更新索引再装,不要图快直接上。

# apt 系:一次装齐,缺一个都可能让某个 filter 静默失效 apt-get update apt-get install -y build-essential pkg-config \ zlib1g-dev libpng-dev libjpeg-dev libtiff-dev \ libfreetype6-dev libfontconfig1-dev libusb-1.0-0-dev \ libavahi-client-dev libgnutls28-dev libpam0g-dev \ libpaper-dev gettext
# dnf/yum 系:注意 libjpeg 在这里叫 libjpeg-turbo-devel dnf install -y gcc gcc-c++ make pkgconfig \ zlib-devel libpng-devel libjpeg-turbo-devel libtiff-devel \ freetype-devel fontconfig-devel libusb1-devel \ avahi-devel gnutls-devel pam-devel \ libpaper-devel gettext

这里的取舍点在于 TLS 后端:gnutls 和 openssl 装哪个,configure 就优先用哪个,两个都装的时候结果取决于探测顺序。我一般只装一个,让链接目标确定下来,省得后面排查证书问题时还要先确认 cupsd 到底链的哪套库。

2.3 编译前自检:pkg-config 名字和包名不是一回事

装完包别急着 configure,先拿 pkg-config 过一遍。库的 pkg-config 名字和包管理器里的包名经常对不上,比如libtiff-dev对应的探测名是libtiff-4,libusb-1.0-0-dev对应libusb-1.0。名字写错,pkg-config 返回非零,configure 就当这个库不存在。

# 逐个探测 CUPS 会用到的库,MISSING 的那行回去补包 for p in zlib libpng libjpeg libtiff-4 freetype2 fontconfig \ libusb-1.0 avahi-client gnutls; do printf '%-14s ' "$p" pkg-config --modversion "$p" 2>/dev/null || echo "MISSING" done

输出里出现 MISSING 就去补对应的开发包,全部有版本号再往下走。这一步看着啰嗦,但比 configure 完事之后发现「怎么 avahi 没编进去」再重来一遍省时间。

3. 编译与安装:configure 开关怎么取、文件最终落在哪

3.1 解包与看帮助

拿到 cups-2.3.3-source.tar.gz 之后,先确认顶层目录名,再解压。

# 先看一眼包的顶层目录,正常应该是 cups-2.3.3/ tar -tzf cups-2.3.3-source.tar.gz | head -5 tar -xzf cups-2.3.3-source.tar.gz cd cups-2.3.3

进目录后不要直接 configure,先看一遍帮助里跟目录和功能相关的开关,把实际支持的选项名记下来,因为有些第三方重新打包过的源码包会改掉个别开关名。

# 只看目录类、功能类开关,输出不长,值得逐条读 ./configure --help | grep -E '^ --(with|enable|disable)' | head -40

我习惯直接在源码目录里 configure,不走 out-of-tree 构建。CUPS 的 Makefile 对 VPATH 的支持不算好,out-of-tree 编完再 install,偶尔会出现路径拼接对不上的情况,为省这点磁盘空间不值得。源码目录留在/usr/local/src或者/opt/src下,编完不急着删,后面排查时要回来看编译日志。

3.2 configure 关键开关逐条过

下面是 2.3.3 上我常用的一套参数,前缀定在/usr,和系统里其它组件共用一套路径。

./configure \ --prefix=/usr \ --sysconfdir=/etc \ --localstatedir=/var \ --with-cups-user=lp \ --with-cups-group=lp \ --with-dnssd=avahi \ --with-tls=openssl \ --disable-debug \ --disable-static

逐条说清楚每个参数改了什么:

  • --prefix=/usr:程序本体装到/usr/sbin、/usr/lib/cups、/usr/share/cups。如果不想覆盖系统包,把前缀改成/opt/cups,代价是后面所有路径、systemd 单元、客户端默认 socket 位置都得跟着改。
  • --sysconfdir=/etc:配置文件进/etc/cups。这是 CUPS 约定俗成的路径,改到别处会出现 cupsd 找不到cups-files.conf的情况,除非同步改启动参数。
  • --localstatedir=/var:队列暂存、日志、状态文件落在/var/spool/cups、/var/log/cups、/var/cache/cups。
  • --with-cups-user/--with-cups-group:cupsd 运行身份,默认就是lp。系统里已有别的打印服务占用这个用户时,这里改成专用账户,后面cups-files.conf里也要同步。
  • --with-dnssd=avahi:启用 Bonjour 发现。不需要局域网自动发现就写--with-dnssd=no,能少一个运行期依赖。
  • --with-tls=openssl:显式指定 TLS 后端。你手上这份包如果不认这个开关,--help里搜 tls 就能看到它实际认什么写法,别硬套。
  • --disable-debug:关掉调试符号,二进制小一圈。要抓 cupsd 的详细日志时,LogLevel debug就够用,不需要重新编译。
  • --disable-static:只生成共享库libcups.so,不生成.a。除非有老程序必须静态链接,否则没必要。

configure 跑完会输出一屏检测结果,逐行扫一遍yes/no。重点看 avahi、openssl/gnutls、usb、pam 这几项,期望是 yes 的项显示 no,就回去补开发包再重新 configure,不要带着残缺的探测结果继续 make。

3.3 make 与 make install,以及装完之后的目录对照

编译按 CPU 核数并行,装的时候必须 root。

# 并行编译,nproc 拿到核数;编译失败时先看最后 30 行 make -j"$(nproc)" # 安装;已经装过旧版的话,这一步会覆盖二进制但一般不动已有配置 make install # 装到 /usr/lib 下的共享库刷新一下缓存,否则 cupsd 启动时报找不到 libcups.so.2 ldconfig

装完之后对照下面这张表确认落地位置,后面配置和排错全靠这些路径。

路径内容备注
/usr/sbin/cupsd调度器主程序-f前台运行,-t只测配置
/usr/lib/cups/backend/后端,usb://、ipp://、socket://都靠它必须是可执行权限
/usr/lib/cups/filter/格式转换 filter缺 cups-filters 时这里会很少
/etc/cups/cupsd.conf监听、访问控制、日志级别常用改这里
/etc/cups/cups-files.conf运行用户、组、目录、日志路径User/Group 在这
/etc/cups/printers.conf队列定义由 lpadmin 自动写,别手改
/var/spool/cups/作业暂存属主必须是运行用户
/var/log/cups/error_log主要排错入口tail -f常驻
/run/cups/cups.sock本地域套接字客户端默认走这里

4. 启动与最小配置:cups-files.conf、cupsd.conf 和 systemd

4.1 运行用户与 lpadmin 组

cupsd 以什么身份跑、谁能做管理操作,这两件事都在cups-files.conf里定,不在cupsd.conf。这是 2.x 和更早版本最大的差别。

# /etc/cups/cups-files.conf User lp Group lp # 管理组,组内成员能通过认证执行增删队列等操作 SystemGroup lpadmin # 日志文件位置 AccessLog /var/log/cups/access_log ErrorLog /var/log/cups/error_log PageLog /var/log/cups/page_log

把日常运维账号加进管理组,让它不用 root 也能 lpadmin:

# 组不存在时先建,再把人加进去 getent group lpadmin || groupadd -r lpadmin usermod -aG lpadmin deploy # deploy 换成你自己的账号 # 加组后必须重新登录(或 newgrp lpadmin)才生效 id

id输出里没看到 lpadmin 就别急着测 lpadmin,那是没生效,不是权限配错。另外/var/spool/cups的属主要和这里的 User 一致,装过旧版 CUPS 的机器上这个目录可能属于别的用户,启动时会报目录不可写。

4.2 cupsd.conf 里必须动的地方

默认生成的cupsd.conf是监听所有地址的,内网打印服务器这样配问题不大,但如果是台有公网口的机器,631 端口敞开意味着任何人都能提交作业。最小改动是只监听需要的地址。

# /etc/cups/cupsd.conf # 只监听本机回环和内网地址,需要别的机器直连就换成对应 IP Listen 127.0.0.1:631 Listen 192.168.10.20:631 Listen /run/cups/cups.sock # Web 管理界面,方便看队列状态;不需要就改 No WebInterface Yes DefaultAuthType Basic LogLevel warn MaxLogSize 1m PreserveJobHistory Yes # 访问控制:Order 决定先看 Allow 还是先看 Deny <Location /> Order allow,deny Allow @LOCAL </Location> # 管理接口只放开本地子网,别用 Allow all <Location /admin> Order allow,deny Allow @LOCAL </Location> <Location /admin/conf> Order allow,deny Allow @LOCAL </Location>

Allow @LOCAL表示同网段来源放行,比Allow all安全得多。Order allow,deny的判定顺序是先匹配 Allow 再匹配 Deny,写反了会出现「明明在同网段却一直 403」。改完先别重启,用cupsd -t做语法检查,它会明确告诉你哪一行有问题。

# 只检查配置,不启动 /usr/sbin/cupsd -t

4.3 systemd 托管与开机自启

源码安装不会自动生成服务单元,自己写一个。关键是Type:如果 configure 时没开 systemd 相关支持,cupsd 不会发 sd_notify 通知,Type=notify会让 systemd 一直等到超时然后判失败。

# /etc/systemd/system/cups.service [Unit] Description=CUPS Scheduler After=network.target [Service] # 没编进 systemd 支持就用 simple,别用 notify Type=simple ExecStart=/usr/sbin/cupsd -f ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure RestartSec=3 [Install] WantedBy=multi-user.target

-f是前台运行,交给 systemd 托管时必须加,否则 cupsd 自己 daemon 化,systemd 会以为进程退了然后反复重启。

systemctl daemon-reload systemctl enable --now cups systemctl status cups --no-pager

4.4 加第一个队列:lpinfo 找设备,lpadmin 建队列

设备用lpinfo -v列,驱动用lpinfo -m列。前者输出里direct usb://...是本地 USB,network ipp://...是网络打印机。

# 列出 cupsd 当前能看到的设备 lpinfo -v # 列可用 PPD/驱动,输出很长,按关键字过滤 lpinfo -m | grep -i -E 'postscript|pcl|everywhere' | head

建队列分两种情形。打印机支持 IPP Everywhere 就直接用-m everywhere,不依赖厂商 PPD;老机器只能走 PPD。

# 支持 IPP Everywhere 的网络打印机 lpadmin -p office-a -E \ -v ipp://192.168.10.50/ipp/print \ -m everywhere \ -o printer-is-shared=true # 只有 PPD 的老机器,-E 表示启用并接受作业 lpadmin -p lab-b -E \ -v usb://Vendor/Model%20Name \ -P /usr/share/cups/model/vendor-model.ppd # 设默认打印机并放行 lpadmin -d office-a lpstat -p -d

-E不能省,它同时做两件事:启用队列、接受作业。少了它队列建出来是停用状态,lpstat -p显示 disabled,提交的作业全堆在那不动。

5. 常见问题排查:源码装 CUPS 最容易翻车的五处

5.1 编译期报找不到 zlib 或 libpng

现象:configure 输出里zlib一行是 no,或者 make 阶段报cannot find -lpng,但包管理器明明显示已安装。

原因:装的是运行库不是开发包,头文件没落地。另一种情况是库装在非标准前缀下,pkg-config 搜不到.pc文件。

解决:确认装的是-dev/-devel结尾的包,再回 2.3 节那段 pkg-config 循环过一遍。库在自定义路径时补环境变量:export PKG_CONFIG_PATH=/opt/lib/pkgconfig:$PKG_CONFIG_PATH,configure 前设好。还有一种罕见情况:系统里同时存在两个版本的 zlib,链接时取到了老的,ldconfig -p | grep libz看一眼实际指向。

5.2 cupsd 启动即退出,日志里是 Address already in use

现象:systemctl start cups立刻 failed,/var/log/cups/error_log里是Unable to bind socket for address 0.0.0.0:631。

原因:系统自带的老 cupsd 还在跑,端口被占;或者上一次 cupsd 异常退出留下了 socket 文件没清。

解决:先看谁占着端口,停掉旧服务再启新的。

ss -lntp | grep 631 systemctl stop cups # 老服务名可能相同,源码安装后会被覆盖,先停再启 rm -f /run/cups/cups.sock systemctl start cups

如果是源码装到了/opt/cups而系统服务还在,两个服务同名会互相覆盖,这种情况建议直接卸掉系统包或者禁用它,别让两套共存。

5.3 lpadmin 报 Forbidden 或 Unauthorized

现象:执行lpadmin -p ...返回lpadmin: Forbidden,或者 Web 界面进/admin一直弹认证框。

原因:当前账号不在cups-files.conf的SystemGroup里;加进组后没有重新登录,会话里还是旧的组信息;或者cupsd.conf里/admin的访问控制没放开@LOCAL。

解决:id看一眼有没有 lpadmin,没有就newgrp lpadmin或者重新登录。确认SystemGroup lpadmin这行没写错,改完systemctl reload cups。认证方式如果是DefaultAuthType Basic,密码走的是系统 PAM,账号本身要能登系统。

5.4 作业一直 pending,error_log 里是 Filter failed

现象:lpstat -o能看到作业,状态停在 pending 或者 processing,纸一动不动;error_log里反复出现Filter failed。

原因:多半是 filter 缺依赖。CUPS 本体只带基础 filter,PDF 转光栅要靠 cups-filters 和 ghostscript,这两样源码包里没有,得单独装。另一种是/usr/lib/cups/filter/下的脚本丢了执行权限。

解决:补装 cups-filters 和 ghostscript,然后检查 filter 权限。

# 看某台打印机最近一次作业的详细日志 grep -A 20 "$(lpstat -W completed -o | tail -1 | awk '{print $1}')" /var/log/cups/error_log # filter 目录下所有文件都该有可执行位 ls -l /usr/lib/cups/filter/ | grep -v '^-rwx'

权限修复用chmod 755批量补一遍。如果是 PPD 引用了不存在的 filter,那只能换驱动,日志里会写出具体缺哪个二进制。

5.5 打印出来是空白页或者乱码

现象:作业状态显示 completed,page_log也有记录,纸出来是白的,或者一堆乱码字符。

原因:PPD 和实际机型不匹配,最常见的是拿 PostScript 驱动去驱动只支持 PCL 的机器;或者页面尺寸配错,内容被推到纸外。

解决:先确认机型支持什么页面描述语言,再换对应 PPD。临时验证可以用-o覆盖页面尺寸发一个测试作业,看是不是尺寸问题。

# 发一个纯文本测试页,绕开 PPD 的格式转换 lp -d lab-b -o media=A4 /etc/cups/cupsd.conf # 查作业实际用了什么参数 lpstat -l -o

纯文本能打、PDF 打不出来,问题就在 PDF 到光栅的那段 filter 上,回 5.4 处理。两种都空白,基本可以断定是驱动选错。

6. 验证跑通:从 lpstat 到一张真正出来的纸

配置改完、服务起来,别急着提交作业,按下面这条链路一层层验,出问题时能立刻定位到哪一段断了。

顺序命令期望结果不对时看哪
1cups-config --version输出 2.3.3装的还是系统旧版,查which cups-config
2cupsd -t无输出,退出码 0报行号就是配置语法错
3systemctl is-active cupsactivejournalctl -u cups -n 50
4lpstat -rscheduler is runningcupsd 没起或 socket 路径不对
5lpinfo -v至少列出一个设备backend 目录权限或设备没插好
6lpstat -p -d队列 idle,默认打印机有标记队列 disabled,重跑 lpadmin -E
7发作业纸出来tail -f /var/log/cups/error_log

真正提交作业的时候,建议同时开两个终端:一个tail -f /var/log/cups/error_log,一个跑命令。

# 终端 A:盯着日志,作业进来会立刻打印处理链路 tail -f /var/log/cups/error_log # 终端 B:发一个测试作业,然后看队列和页计数 lp -d office-a /etc/cups/cupsd.conf lpstat -o tail -3 /var/log/cups/page_log

page_log里出现一行记录,说明作业走完了整个 filter 链、被 backend 送出去了。这时候纸还没出来,问题就已经不在 CUPS 这一层了,去查打印机本身、网络连通性和队列名对不对。日志里如果卡在[Job xx] Started filter之后不动,那就是 filter 环节,回 5.4;卡在Started backend之后,就是设备侧,先lpinfo -v确认设备还在不在。

队列卡死清理用cancel -a,把待处理作业全清掉再重来,比一个个 cancel 快。验证阶段还有一个容易忽略的点:preserve job history开着的时候,历史作业会一直占着/var/spool/cups,测试阶段无所谓,长期跑建议定期cancel -a或者关掉历史保留。

从那以后我每次源码装完 CUPS,都会强制走一遍这七步验证,哪怕只是给测试机搭个环境——因为「服务起来了」和「能打纸」之间隔着 filter、backend、权限三道门,肉眼是看不出来的,只有日志和 page_log 会说实话。这份 cups-2.3.3-source.tar.gz 拿到手,先对一下顶层目录名是不是cups-2.3.3,再按第 3 章那套 configure 参数走一遍,剩下的就是耐心看日志。希望帮到你。

本文还有配套的精品资源,点击获取