Есть у меня дома лазерник Samsung ML-1615 — древний, 2005 года, но печатает исправно и тонер ещё не кончился, выкидывать жалко. Проблема одна: подключается только по USB, и который год он был воткнут в комп на столе — печать «с телефона» означала «переслать себе файл и подойти к компу». Решил это исправить: AirPrint через маленький LXC-контейнер на Proxmox. Оказалось не так просто, как «apt install cups» — принтер сначала соврал iPhone, что умеет двустороннюю печать, потом стал мигать красным и есть страницы. Расскажу по порядку.
Схема
Принтер воткнут по USB прямо в хост Proxmox. Отдельный privileged LXC-контейнер (512 МБ RAM хватает за глаза) получает USB по пробросу, крутит CUPS и объявляет очередь по mDNS/DNS-SD через Avahi — iPhone и Mac видят его в сети как обычный AirPrint-принтер.

Первая мелкая засада: ML-1615 внутри опознаётся как «ML-1610 Mono Laser Printer» — тот же движок, та же прошивка, просто перелейбленный корпус. И говорит он не PCL и не PostScript, а SPL2/QPDL — фирменным языком Samsung, для которого в Linux есть только один драйвер: printer-driver-splix.
USB-проброс в LXC
Контейнер сделан privileged, чтобы не возиться с udev-правилами под непривилегированным UID-сдвигом. В /etc/pve/lxc/<id>.conf:
lxc.cgroup2.devices.allow: c 189:* rwm
lxc.cgroup2.devices.allow: c 180:* rwm
lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir
lxc.mount.entry: /dev/usb dev/usb none bind,optional,create=dir
Суть в том, чтобы забиндить весь /dev/bus/usb, а не конкретное устройство — тогда переподключение принтера или смена номера USB-порта не требует лезть в конфиг. И важно проверить, что на самом хосте Proxmox не висит своего CUPS/cupsd — иначе он first-come-first-served захватит принтер, а контейнер получит унылое «device busy».
Пакеты внутри контейнера — стандартный набор:
apt install cups cups-filters cups-filters-core-drivers \
printer-driver-splix avahi-daemon avahi-utils dbus
cups-filters-core-drivers — отдельно подчеркну, обязателен. Без него не объявляется MIME-тип image/urf, и печать с iPhone молча не работает, даже если очередь прекрасно видна в сети.
В cupsd.conf — слушать не только localhost, разрешить локалку и включить публикацию через Avahi:
Listen 0.0.0.0:631
BrowseLocalProtocols dnssd
# + Allow @LOCAL в <Location /> и <Location /admin>
cupsctl --remote-admin --share-printers
И сама очередь:
lpadmin -p ML1615 -E \
-v "usb://Samsung/ML-1610?serial=<серийник>" \
-m "drv:///splix-samsung.drv/ml1610.ppd" \
-D "Samsung ML-1615 (AirPrint)" \
-o printer-is-shared=true -o media=a4
media=a4 выставил сразу — иначе очередь по умолчанию встаёт на американский Letter, и потом удивляешься, почему поля съехали.
На этом моменте всё выглядело идеально: тестовая страница ушла, avahi-browse показывает очередь, iPhone принтер видит. Победа? Как бы не так — она началась именно тут.
Задание «completed», а бумага не выходит
Первый настоящий документ с iPhone (три страницы, PDF) CUPS честно отчитал как успешный — а принтер ничего не напечатал. Включил LogLevel debug и пошёл разбирать по слоям.
1. Задержка в 25-30 секунд на каждое задание
Растровый фильтр gstoraster для каждого задания стучится в DBus, к org.freedesktop.ColorManager, за цветовым профилем. А служба colord в LXC падает с 226/NAMESPACE — классическое ограничение контейнера на mount-namespacing systemd-юнитов с харденингом. DBus честно ждёт активации сервиса до таймаута (25 секунд), потом фильтр продолжает без профиля — не страшно для чёрно-белого принтера, но каждое задание тормозит почти на полминуты.
Лечится одной командой — сервис для монохромного принтера попросту не нужен:
systemctl mask colord
Маскирование даёт DBus мгновенный отказ вместо ожидания таймаута несуществующего сервиса — время created→completed упало с 60-90 секунд до 37.
2. Ядро тоже хочет дружить с принтером
На хосте Proxmox родной драйвер ядра usblp сам подхватывает USB-интерфейс принтера, конкурируя с прямым libusb-доступом backend’а CUPS. В логе это выглядело так: данные полностью ушли (Sent N bytes), а потом — Got USB transaction timeout during read и сброс принтера.
Отвязал руками и закрепил udev-правилом, чтобы не повторялось при переподключении:
# руками, сразу
echo -n '1-1:1.0' > /sys/bus/usb/drivers/usblp/unbind
# /etc/udev/rules.d/99-no-usblp-ml1610.rules
ACTION=="bind", SUBSYSTEM=="usb", DRIVER=="usblp", \
ATTRS{idVendor}=="04e8", ATTRS{idProduct}=="3268", \
RUN+="/bin/sh -c 'echo -n $kernel > /sys/bus/usb/drivers/usblp/unbind'"
Забавный нюанс: после отвязки usblp точно тот же паттерн read-timeout/reset иногда всё равно проскакивал в логах на следующих тестовых заданиях. То есть конфликт с драйвером ядра был реальным и его стоило устранить, но первопричиной пропавшей печати он не был — это, похоже, штатное поведение backend’а CUPS именно для этой модели. Настоящая причина крылась глубже.
3. Мигающий красный светодиод = принтеру не хватило памяти
У ML-1610/1615 по документации Samsung мигающий (не сплошной!) красный означает переполнение буфера: «If the printer does not have enough memory… simplify the page layout». А памяти там — смешные 2 МБ. Документ, отрендеренный HeadlessChrome в PDF с тяжёлой графикой, на дефолтных 600 dpi — это уже растр порядка 4300×6800 пикселей на A4, то есть мегабайты на страницу. Первая страница напечаталась, дальше принтер захлебнулся.
Временное лечение нашлось сразу — понизить разрешение:
lpadmin -p ML1615 -o Resolution=300dpi
На 300 dpi тот же файл пропечатался целиком без единого сбоя.
4. Почему с айфона нельзя просто выбрать пониже разрешение
Логичный вопрос: раз проблема в разрешении, пусть пользователь просто ставит 300 dpi в диалоге печати, когда видит, что документ сложный. Не вышло — и тут открылась забавная асимметрия. Компьютер под Windows, печатающий на ту же очередь через ipps://, реально передаёт в IPP-запросе атрибут printer-resolution, и CUPS честно матчит его на PPD-опцию. А AirPrint с iOS этот атрибут вообще не шлёт — только грубый print-quality (draft/normal/best), который наш PPD никак не связывает с DPI. Сколько ни листай варианты в диалоге печати на телефоне — там просто нет контрола, который реально долетит до Resolution. С сервера явный lp -o Resolution=600dpi работает штатно — дело не в CUPS, а в том, что iOS для этой очереди в принципе не умеет попросить нужное DPI.
Автоматику «сам определи, влезет или нет» тоже сделать не вышло: логи заведомо успешного (300 dpi) и заведомо провального (600 dpi, тот же файл) задания выглядят абсолютно одинаково — Job completed, backend отчитал «отправлено N байт» без единой ошибки в обоих случаях. Переполнение случается уже внутри самого принтера, после того как USB-передача целиком завершилась — программная сторона физически не получает об этом никакого сигнала обратно.
В итоге решил осознанно: дефолт очереди — 600 dpi (качество важнее), а если конкретный документ не допечатался и принтер мигает красным — вручную даю команду на 300 dpi, перепечатываю, потом при желании верну обратно.
5. Принтер, который врал про дуплекс
Отдельная находка: у ML-1610 физически нет автоматической двусторонней печати — бюджетная модель 2005 года, в механике попросту нет узла переворота листа. Но универсальный PPD драйвера splix всё равно объявлял опцию Duplex: None/DuplexNoTumble/DuplexTumble — и это транслировалось прямо в AirPrint-объявление по mDNS (TXT-запись показывала Duplex=T). То есть принтер буквально врал айфону, что умеет печатать с двух сторон.
Сопоставление логов показало, что запрос двусторонней печати определённо усугублял и без того шаткое положение принтера на грани его 2 МБ памяти. Полечил хирургически — вычистил лишние опции прямо из установленного PPD:
# в /etc/cups/ppd/ML1615.ppd убрать:
# *Duplex DuplexNoTumble...
# *Duplex DuplexTumble...
# оставить только:
# *Duplex None/Off (1-Sided)
rm /var/cache/cups/ML1615.data /var/cache/cups/ML1615.strings
systemctl restart cups avahi-daemon
После этого lpoptions -p ML1615 -l показывает только *None, а TXT-запись AirPrint больше не врёт про дуплекс. Единственный нюанс — любая будущая переустановка драйвера через lpadmin -m перезатрёт PPD системным шаблоном, и правку придётся повторять.
Итог
Принтер 2005 года печатает по AirPrint с айфона и мака через крошечный LXC на Proxmox, вообще без компа-посредника. Ушло на это куда больше времени, чем «apt install cups» — но каждая находка по пути была по-своему занятной: DBus, ждущий несуществующий сервис; ядро, дерущееся с CUPS за один и тот же USB-интерфейс; принтер, безбожно завышающий свои возможности перед телефоном; и айфон, который в принципе не умеет попросить нужное разрешение печати. Если у вас пылится такой же ветеран под столом — восстанавливать его в бытовом железе вокруг Proxmox определённо стоит.
Шпаргалка на будущее, если печать вдруг встанет колом:
- «device busy» → проверить, что CUPS не поднялся на хосте Proxmox или в другом контейнере с тем же пробросом;
- принтер виден в avahi внутри контейнера, но не с телефона → устройства не в одном L2-сегменте, mDNS не маршрутизируется;
- мигает красный во время/после печати → переполнение памяти, штатно для этой серии, лечится понижением DPI;
- сплошной красный (не мигающий) → это уже механика: бумага/замятие/тонер, тут логи не помогут;
- каждое задание тормозит на ~25 секунд → проверить, что
colordзамаскирован.
