cpus打印服务

http://ip:631/

一键网络打工单

网络打印机

lpd协议 515 TCP 端口 打印机 konica bizhub C368

打印机地址: lpd://192.168.20.200/queue  

同时上传驱动: KOC759SCX.ppd  

ipp协议打印

  • 创建打印队列 create-job 0x0006 #包含打印参数:纸张大小和文件名

  • 发送打印文件 send-Document 0x0007 #包含文件名和内容

  • 单次打印 Print-Job 0x0002 (常用)

查询纸张支持media-supported

cups

  • 使用PageSize支持纸张
  • 设置纸张宽高单位往往是 点(points)

papercut

  • 实际打印为 PostScript(PS)文件 (建议pdf)

  • 使用media-size中y-dimension与x-dimension 支持纸张 ??

  • 纸张宽高单位微米

命令行打印

需要打印机支持文本打印

ipp打印

echo "100200300400500hello110220330440550" | lp -d Xprinter_XP-420B  #打印到打印机

echo "100200300400500hello110220330440550" | lp   -h 192.168.12.105:631 -d Xerox_Xprinter_XP-420B  -o PageSize=Custom.120x170
echo "100200300400500hello110220330440550" | lp -h 192.168.12.105:631 -d Xerox_Xprinter_XP-420B #打印机内容

lp   -h 192.168.12.105:631 -d Xerox_Xprinter_XP-420B -o PageSize=Custom.120x170 ~/d.pdf
lp   -h 192.168.12.98:9163 -d "Xprinter XP-420B"   ~/d.pdf

lp -d ipp://192.168.12.98:9163/printers/Xprinter%20XP-420B ~/d.pdf #????

lp -d  Xprinter_XP-420B /mnt/d.pdf
lp -d EPSON_LQ-630KII document.pdf   #打印文件


#远程打印 
lp -h 192.168.12.105:631 -d Xerox_Xprinter_XP-420B ./d.pdf  

lpq -h 192.168.12.105:631 -P Xerox_Xprinter_XP-420B  #获取打印队列
lpq -h 192.168.12.98:9163 -P "Xprinter XP-420B"  #支持

lp -d Xerox_Xprinter_XP-420B -o PageSize=Custom.120x160  /mnt/d.pdf #实际纸张40*60 小的在前面

ipp管理



lpstat -a    #发现网络 打印机列表 ??
 

 lpoptions  -h 192.168.12.105:631 -d Xerox_Xprinter_XP-420B  -l

 lpoptions  -h 192.168.12.98:9163 -d "Xprinter XP-420B"   -l #不支持
lpstat -h 192.168.12.105:631 -a
lpstat -h 192.168.12.98:9163 -a #不支持 papercut

内容管理

pdfinfo d.pdf | grep "Page"  #检查pdf文件逻辑大小 

pdfinfo output.pdf | grep "Page"  #检查pdf文件逻辑大小 

ps2pdf file.ps temp.pdf  #文件转换

裁剪pdf

gs -o out_40x60.pdf \
   -sDEVICE=pdfwrite \
   -dFIXEDMEDIA \
   -dPDFFitPage \
   -dDEVICEWIDTHPOINTS=113.39 \
   -dDEVICEHEIGHTPOINTS=170.08 \
   d.pdf

查看打印机

解压deb文件可以得到ppd驱动 例如

lpinfo -m  #(已经支持的驱动)

lpinfo -v      #(插入后,看到打印机)
lpstat -p -d   #(安装完成后)
lpoptions -d   XP-420B  #打印机选项 -设置纸张自定义 -o PageSize=Custom.120x160

cupsenable XP-420B  #启用打印机
cupsaccept XP-420B

lpstat -l -o   #打印队列明细







ipp内容

version: 2.0
operation-id: Print-Job 0x0002

name: attributes-charset
charset value: 'utf-8'

name: attributes-natural-language
naturalLanguage value: 'zh-cn'

name: PageSize
nameWithoutLanguage value: 'Custom.120x170'

name: print-color-mode
keyword value: 'monochrome'

name: requesting-user-name
nameWithoutLanguage value: 'jack'

name: printer-uri
uri value: 'ipp://192.168.12.105:631/printers/Xerox_Xprinter_XP-420B'

环境管理

apt install lpr cups-client foomatic-db 
dpkg-query -W libcups2

docker-compose  -f /vol1/1000/cups/docker-compose.yml  up -d

docker exec -ti  ydkn-cups bash #最好自己构建

foomatic驱动官网

过滤器错误

XP-420B Idle - "Rendering completed"  #打印机状态
stopped  "Filter failed"   #打印队列状态

依赖安装

apt install cups-ipp-utils
 

apt install libcupsimage2   # libcupsimage2t64 代替 libcupsimage2

apt install libcups2 cups-filters ghostscript libcupsimage2t64
apt remove printer-driver-xprinter #重装

安装驱动

dpkg --force-all -i /mnt/epsidm24-secc0014_1_2_4-1_amd64.deb

lpinfo -m  | grep -i 630k

direct usb://EPSON/LQ-630KII?serial=52323130315E4A0505
Epson/eplq630kii-Epson_epsidm24-secc0014.ppd Epson LQ-630KII ESC/P2 secc0014, 1.2

安装驱动

lpadmin -p Xprinter_XP-420B -E -v usb://Xprinter/XP-420B?serial=S420BUH25A160664 -P            /usr/share/cups/model/xprinter/XP-420B.ppd


构建

vi Dockerfile

# 基础镜像使用 Debian 12 (Bookworm),以确保使用最新的稳定版本和库
FROM debian:12-slim

# 维护者信息
LABEL maintainer="Your Name <your.email@example.com>"

# 设置环境变量,用于 CUPS 配置(与您的 compose 文件一致)
ENV CUPSADMIN=admin \
    CUPSPASSWORD=admin \
    TZ=Asia/Shanghai \
    # 强制 CUPS 在前台运行,这是 Docker 容器的标准做法
    CUPSD_FOREGROUND=yes \
    # 设置默认配置目录
    CUPS_SERVER_ROOT=/etc/cups \
    # 设置 CUPS 状态文件目录
    CUPS_STATE_DIR=/var/run/cups

# 刷新包列表并安装 CUPS 及其相关组件、系统工具
# - cups: CUPS 服务本身
# - cups-client: 客户端工具
# - cups-bsd: 兼容 BSD 的打印命令 (lpr)
# - printer-driver-all: 尽可能多的打印机驱动
# - ghostscript: 打印处理依赖
# - sudo: 用于创建 CUPS 用户的可能需求 (虽然在 Dockerfile 中不常用)
# - locales/tzdata: 用于时区和语言环境设置
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
    cups libcupsimage2 libcupsimage2 \
    cups-client \
    cups-bsd \
    printer-driver-all \
    ghostscript \
    sudo \
    locales \
    tzdata \
    # 用于 USB 设备和 DBUS 依赖 (如果需要)
    dbus \
    udev \
    # 清理 APT 缓存,减小镜像大小
    && rm -rf /var/lib/apt/lists/*

# 配置时区
RUN echo "${TZ}" > /etc/timezone && \
    dpkg-reconfigure --frontend noninteractive tzdata

# 为 CUPS 创建必要的目录和权限
# /var/run/cups 和 /var/cache/cups 可能是动态的,但确保它们存在且权限正确
RUN mkdir -p /var/run/cups /var/cache/cups /var/log/cups /etc/cups/ppd /usr/lib/cups/backend && \
    chmod -R 755 /var/run/cups /var/cache/cups /var/log/cups /etc/cups/ppd && \
    # CUPS 默认运行的用户和组通常是 'lp' 和 'sys'
    chown -R root:lp /var/run/cups /var/cache/cups /var/log/cups /etc/cups/ppd && \
    # 确保 /etc/cups/cupsd.conf 的权限正确
    chmod 644 /etc/cups/cupsd.conf

# 准备 CUPS 初始配置脚本
COPY setup_cups.sh /usr/local/bin/setup_cups.sh
RUN chmod +x /usr/local/bin/setup_cups.sh

# 暴露 CUPS 默认端口
EXPOSE 631

# 容器启动时运行的命令
# 1. 运行配置脚本,设置管理用户并配置 cupsd.conf
# 2. 以调试/前台模式启动 CUPS 守护进程,这是 Docker 容器保持运行的标准做法
CMD ["/bin/bash", "-c", "/usr/local/bin/setup_cups.sh && /usr/sbin/cupsd -f"]

# 在构建的同一目录下,您还需要创建配置脚本 setup_cups.sh
docker build -t my-cups .

启动配置sh

vi setup_cups.sh

#!/bin/bash

# 设置 CUPS 配置文件路径
CUPS_CONF="/etc/cups/cupsd.conf"

echo "### Starting CUPS initial configuration..."

# 检查 CUPS 管理员用户是否存在,如果不存在则创建
if ! id -u "$CUPSADMIN" >/dev/null 2>&1; then
    echo "Creating CUPS admin user: $CUPSADMIN"
    # 创建用户并设置密码
    # -M: 不创建家目录
    # -s /bin/false: 设置一个不可登录的shell
    # -G lpadmin: 将用户添加到lpadmin组,使其成为CUPS管理员
    useradd -r -M -s /bin/false -G lpadmin "$CUPSADMIN"
    # 使用环境变量设置密码
    echo "$CUPSADMIN:$CUPSPASSWORD" | chpasswd
    echo "CUPS admin user created successfully."
else
    echo "CUPS admin user $CUPSADMIN already exists."
fi

# 确保 CUPS 服务在前台运行的配置是启用状态
if [ "$CUPSD_FOREGROUND" = "yes" ]; then
    echo "Setting up CUPS to listen on all interfaces (0.0.0.0:631) and enable web interface."
    # 允许监听所有网络接口 (0.0.0.0:631)
    sed -i 's/Listen localhost:631/Listen 0.0.0.0:631/' $CUPS_CONF

    # 开放 web 界面访问
    # 允许来自所有主机的远程管理(需要谨慎)
    sed -i '/<Location \/>/,/<\/Location>/ s/Order deny,allow/Allow All\nOrder allow,deny/' $CUPS_CONF
    sed -i '/<Location \/admin>/,/<\/Location>/ s/Order deny,allow/Allow All\nOrder allow,deny/' $CUPS_CONF
    sed -i '/<Location \/admin>/,/<\/Location>/ s/Require user @SYSTEM/AuthType Basic\nRequire user @SYSTEM/' $CUPS_CONF
    sed -i '/<Location \/admin\/conf>/,/<\/Location>/ s/Require user @SYSTEM/AuthType Basic\nRequire user @SYSTEM/' $CUPS_CONF
    
    # 启用远程连接
    if ! grep -q "ServerName" $CUPS_CONF; then
        echo "ServerName 0.0.0.0" >> $CUPS_CONF
    fi

    echo "Restarting D-Bus service if running in host mode to ensure device access..."
    # 尽管 network_mode: host 应该处理大部分 D-Bus,但我们尝试在容器内启动 D-Bus(如果需要)
    # 在许多情况下,在 Docker 容器中正确运行 DBUS 是复杂的,但这一步是为了提高兼容性。
    if [ -e /var/run/dbus/system_bus_socket ]; then
        echo "DBus socket found, D-Bus may be functional or mounted from host."
    else
        # 尝试启动一个简单的 DBUS 会话,以满足 CUPS 的一些依赖
        dbus-daemon --system --fork --nopidfile
    fi
fi

echo "### CUPS configuration complete. Starting cupsd in foreground..."
# 检查配置文件是否已修改
grep "Listen 0.0.0.0:631" $CUPS_CONF || echo "Warning: Configuration change might not be applied correctly."

# 将控制权交回给 CMD 以启动 CUPS
exec "$@"

启动yaml

olbat/cupsd

services:
  cups:
    # 使用您刚才构建的镜像名称和标签
    image: my-cups
    container_name: ydkn-cups
    # 必须使用 host 网络模式,才能正确与主机上的打印机和 USB/网络进行通信
    network_mode: host
    privileged: true # 推荐添加 privileged 权限以确保对 /dev/bus/usb 的完全访问
    user: "0"
    volumes:
      # /var/run/dbus 用于系统服务间通信(如 udev/设备发现)
      - /var/run/dbus:/var/run/dbus:ro
      # 您的自定义挂载点
      - /vol1/1000/cups/vmnt:/mnt
      - /vol1/1000/cups/conf:/etc/cups
    devices:
      # 必须挂载整个 USB 总线以进行 USB 打印机发现
      - /dev/bus/usb:/dev/bus/usb
    environment:
      # 这些变量将由 setup_cups.sh 脚本使用
      - CUPSADMIN=admin
      - CUPSPASSWORD=admin
      - TZ=Asia/Shanghai
    restart: always

测试访问

curl -k -u admin:admin https://localhost:631/  #忽略证书验证访问cups
docker-compose  -f /vol1/1000/cups/docker-compose.yml  up -d

cupsd配置

vim /etc/cups/cupsd.conf

添加

ServerName cups-host
Encryption Never
Encryption IfRequested
Listen 0.0.0.0:631

<Location /admin>
  Encryption Never
  Allow All
</Location>

 

https://192.168.12.105:631/admin

快速换镜像

apt install apt-transport-https ca-certificates curl  
cat <<EOF >   /etc/apt/sources.list

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-backports main contrib non-free non-free-firmware

EOF

cat <<EOF >   /etc/apt/sources.list
deb http://deb.debian.org/debian bookworm main contrib non-free
deb http://deb.debian.org/debian bookworm-updates main contrib non-free
deb http://security.debian.org/debian-security bookworm-security main contrib non-free
EOF

抓包分析

用户获取不同打印机: 纸张大小参数

转发设置

netsh interface portproxy add v4tov4 listenport=631 listenaddress=0.0.0.0 connectport=631 connectaddress=192.168.12.105 protocol=tcp


包过滤条件

Wireshark

(  ip.src == 192.168.12.98 and ip.dst == 192.168.12.105 ) && (ipp)
lpoptions -h 192.168.12.105:631 -p Xerox_Xprinter_XP-420B -l|grep -i page



lpoptions -h 192.168.12.98:9163 -p "Xprinter XP-420B"  -l|grep -i page

一键打印


def 一键打印(data,pageSize):
    
    req = b""
    # IPP Header (version 2.0, Print-Job 0x0002, request-id 1)
    req += struct.pack("!BBH", 0x02, 0x00, 0x0002)
    req += ipp_int32(1)
    
    # Operation Attributes
    req += ipp_tag(0x01)
    
    # attributes-charset
    req += ipp_tag(0x47) + ipp_name("attributes-charset") + ipp_text("utf-8")
    
    # attributes-natural-language
    req += ipp_tag(0x48) + ipp_name("attributes-natural-language") + ipp_text("zh-cn")
    
    # printer-uri
    req += ipp_tag(0x45) + ipp_name("printer-uri") + ipp_text(
        f"http://{cups_addr}/printers/{printer_name}"
    )
    
    # requesting-user-name
    req += ipp_tag(0x42) + ipp_name("requesting-user-name") + ipp_text("jack")
    
    # 纸张大小 PageSize
    # size = "CustomSize4"
    # size = "Custom.40x60"
    # size = "Custom.113x170"
    # size = "Custom.120x170"
    # size = "Custom.120x170"
    #size = "Custom.133x170"
    #size = "Custom-10x15cm"
    req += ipp_tag(0x44) + ipp_name("PageSize") + ipp_text(pageSize) #cups纸张参数
    #req += ipp_tag(0x44) + ipp_name("mediaSizes") + ipp_text(size) #papercut纸张参数
    #req += ipp_tag(0x44) + ipp_name("media") + ipp_text(size) #papercut纸张参数  media: "Custom-10x15cm"
    
    # document-format = application/pdf  (keyword tag = 0x44)
    req += ipp_tag(0x44) + ipp_name("document-format") + ipp_text("application/pdf")
    
    # End of attributes
    req += ipp_tag(0x03)

    #req += document_data
    #req += pdf_res.content #document_data
    req += data
    # --------------------------
    # Send IPP request
    # --------------------------
    url = f"http://{cups_addr}/printers/{ parse.quote(printer_name, safe=':/?&=')  }"
    headers = {
        "Content-Type": "application/ipp"
    }
    response = requests.post(url, headers=headers, data=req)
    return response
#----
with open(r"c:\downloaded_file.pdf", "rb") as f:
    document_data = f.read()
response = 一键打印(pdf_res.content,"Custom.120x170")
print("HTTP Status:", response.status_code,int.from_bytes(response.content[2:4]))
 

pdf模板

基于模板的替换 生成新pdf

def 查找替换pdf( page ,kvls,font_size=10,font_path='C:\Windows\Fonts\simhei.ttf'):
    """ pdf某一页,[["<<占位符>>","新内容"],] """
    xp,yp = page.rect[2],page.rect[3]
    add_buffer = BytesIO()
    c = canvas.Canvas(add_buffer, pagesize=(xp,yp ))
    pdfmetrics.registerFont(TTFont('ChineseFont', font_path))
    c.setFont("ChineseFont", font_size)
    for rp in kvls:
        old_text =  rp[0]
        new_text =  rp[1]
        text_instances = page.search_for(old_text)
        drawx = text_instances[0][0] #取得 第一个的x坐标
        drawy = text_instances[-1][1]  #最后一个 y坐标  
        #绘制
        c.drawString(drawx, yp - drawy - font_size, new_text)# y轴起点不一样可能是反的 (简单减去字体大小)
        # 替换找到的文本
        #擦除
        for inst in text_instances:
            # 在原始文本位置添加一个白色矩形覆盖原文本
            page.add_redact_annot(inst, fill=(1, 1, 1))  # 白色填充
        
   
    c.showPage()
    c.save()
    # 合并
    add_buffer.seek(0)
    add_doc = fitz.open(stream=add_buffer, filetype="pdf")
    page.apply_redactions()
    page.show_pdf_page(page.rect, add_doc, 0) #合并第一页
    return add_buffer #新pdf
watermark_buffer = 查找替换pdf(page,[["123456",'新文本123'],["__二维码__","请扫码"]])