From c97f169b776fefad29c5432cc3325e2259896ec0 Mon Sep 17 00:00:00 2001 From: June Date: Sun, 14 Apr 2024 18:46:51 +0200 Subject: [PATCH] Add print server for label printer to have it easily usable via SSH Add and configure a print server for the Brother P-touch QL 500 label printer, so that it can be easily used via SSH. Do the following to make that work: - Configure the print server host. - Package printer-driver-ptouch to have a working driver for the label printer. - Configure CUPS. - Add a script "forcecommand-lpr-wrapper", which works together with the ForceCommand sshd_config option and wraps lpr to provide an easy interface to use the Brother QL 500 label printer via SSH. - Add a print user and configure SSH to have the "forcecommand-lpr-wrapper" script accessible without a password using the print user via SSH. --- .../ptouch-print-server/configuration.nix | 7 ++ config/hosts/ptouch-print-server/default.nix | 9 ++ .../forcecommand-lpr-wrapper.py | 84 ++++++++++++++ .../forcecommand-lpr-wrapper/setup.py | 7 ++ .../hosts/ptouch-print-server/networking.nix | 23 ++++ config/hosts/ptouch-print-server/printing.nix | 104 ++++++++++++++++++ flake.nix | 14 +++ 7 files changed, 248 insertions(+) create mode 100644 config/hosts/ptouch-print-server/configuration.nix create mode 100644 config/hosts/ptouch-print-server/default.nix create mode 100644 config/hosts/ptouch-print-server/forcecommand-lpr-wrapper/forcecommand-lpr-wrapper.py create mode 100644 config/hosts/ptouch-print-server/forcecommand-lpr-wrapper/setup.py create mode 100644 config/hosts/ptouch-print-server/networking.nix create mode 100644 config/hosts/ptouch-print-server/printing.nix diff --git a/config/hosts/ptouch-print-server/configuration.nix b/config/hosts/ptouch-print-server/configuration.nix new file mode 100644 index 0000000..1bb1448 --- /dev/null +++ b/config/hosts/ptouch-print-server/configuration.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + networking.hostName = "ptouch-print-server"; + + system.stateVersion = "23.11"; +} diff --git a/config/hosts/ptouch-print-server/default.nix b/config/hosts/ptouch-print-server/default.nix new file mode 100644 index 0000000..248bae6 --- /dev/null +++ b/config/hosts/ptouch-print-server/default.nix @@ -0,0 +1,9 @@ +{ ... }: + +{ + imports = [ + ./configuration.nix + ./networking.nix + ./printing.nix + ]; +} diff --git a/config/hosts/ptouch-print-server/forcecommand-lpr-wrapper/forcecommand-lpr-wrapper.py b/config/hosts/ptouch-print-server/forcecommand-lpr-wrapper/forcecommand-lpr-wrapper.py new file mode 100644 index 0000000..d42469c --- /dev/null +++ b/config/hosts/ptouch-print-server/forcecommand-lpr-wrapper/forcecommand-lpr-wrapper.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +# A script for usage with the ForceCommand sshd_config option. +# It calls lpr with some standard arguments, but also parses +# SSH_ORIGINAL_COMMAND to potentially provide a different set of arguments to +# lpr. +# +# This wrapper is written for interacting with the Brother QL 500 label printer. +# +# The following options can be provided as an SSH command and this script will +# then pass them to the lpr call: +# - MediaType can be one of: +# - Labels +# - Tape (this is the default) +# - PageSize can be one of: +# - 12mm +# - 12mm-circular +# - 17x54mm +# - 17x87mm +# - 23x23mm +# - 24mm-circular +# - 29mm +# - 29x90mm +# - 38mm +# - 38x90mm +# - 50mm +# - 54mm +# - 58mm-circular +# - 62mm +# - 62x29mm +# - 62x100mm +# - Custom.WIDTHxHEIGHT (with WIDTH and HEIGHT needing to be either one to +# three digits) +# - label-wide (this being a convenience alias for Custom.62x35mm and it also +# being the default) +# - label-item (this being a convenience alias for 38x90mm) +# +# So using these options in a complete setup would look like this for example: +# cat label-item.pdf | ssh print@ptouch-print-server.z9.ccchh.net labels label-item +# This being equivalent to: +# cat label-item.pdf | ssh print@ptouch-print-server.z9.ccchh.net Labels 38x90mm +# +# The options are case-insensitive. +# +# The options are derived from: lpoptions -p Brother-QL-500 -l + +import os, re, subprocess + +mediaType = "Tape" +pageSize = "Custom.62x35mm" + +def parseGivenOptions(): + givenOptionsString = os.environ["SSH_ORIGINAL_COMMAND"] + givenOptionsIterator = iter(givenOptionsString.split(" ")) + + givenMediaType = next(givenOptionsIterator, "") + givenPageSize = next(givenOptionsIterator, "") + + global mediaType + if givenMediaType.lower() == "labels": + mediaType = "Labels" + elif givenMediaType.lower() == "tape": + mediaType = "Tape" + + global pageSize + pageSizeRegex = re.compile(r"^((12mm(-circular)?)|(24mm-circular)|(58mm-circular)|(((17x(54|87))|(23x23)|((29|38)(x90)?)|(62x(29|100))|50|54|62)mm))$", re.ASCII | re.IGNORECASE) + pageSizeMatch = pageSizeRegex.match(givenPageSize) + pageSizeCustomRegex = re.compile(r"^custom\.(\d{1,3})x(\d{1,3})$", re.ASCII | re.IGNORECASE) + pageSizeCustomMatch = pageSizeCustomRegex.match(givenPageSize) + if givenPageSize.lower() == "label-wide": + pageSize = "Custom.62x35mm" + elif givenPageSize.lower() == "label-item": + pageSize = "38x90mm" + elif pageSizeMatch: + pageSize = givenPageSize.lower() + elif pageSizeCustomMatch: + width = pageSizeCustomMatch.group(1) + height = pageSizeCustomMatch.group(2) + pageSize = "Custom.{}x{}".format(width, height) + +if "SSH_ORIGINAL_COMMAND" in os.environ: + parseGivenOptions() + +subprocess.run(["lpr", "-P", "Brother-QL-500", "-o", "MediaType={}".format(mediaType), "-o", "PageSize={}".format(pageSize)]) diff --git a/config/hosts/ptouch-print-server/forcecommand-lpr-wrapper/setup.py b/config/hosts/ptouch-print-server/forcecommand-lpr-wrapper/setup.py new file mode 100644 index 0000000..ef2b170 --- /dev/null +++ b/config/hosts/ptouch-print-server/forcecommand-lpr-wrapper/setup.py @@ -0,0 +1,7 @@ +from distutils.core import setup + +setup( + name = "forcecommand-lpr-wrapper", + version = "0.0.1", + scripts = ["./forcecommand-lpr-wrapper.py"] +) diff --git a/config/hosts/ptouch-print-server/networking.nix b/config/hosts/ptouch-print-server/networking.nix new file mode 100644 index 0000000..7c7cb62 --- /dev/null +++ b/config/hosts/ptouch-print-server/networking.nix @@ -0,0 +1,23 @@ +{ ... }: + +{ + networking = { + interfaces.net0 = { + ipv4.addresses = [ + { + address = "10.31.208.13"; + prefixLength = 25; + } + ]; + }; + defaultGateway = "10.31.208.1"; + nameservers = [ + "10.31.208.1" + ]; + }; + + systemd.network.links."10-net0" = { + matchConfig.MACAddress = "BC:24:11:F2:CF:8F"; + linkConfig.Name = "net0"; + }; +} diff --git a/config/hosts/ptouch-print-server/printing.nix b/config/hosts/ptouch-print-server/printing.nix new file mode 100644 index 0000000..5964f56 --- /dev/null +++ b/config/hosts/ptouch-print-server/printing.nix @@ -0,0 +1,104 @@ +# Sources for this configuration: +# - https://nixos.wiki/wiki/Printing + +{ pkgs, lib, ... }: + +let + # https://github.com/philpem/printer-driver-ptouch + printer-driver-ptouch = pkgs.stdenv.mkDerivation rec { + pname = "printer-driver-ptouch"; + version = "1.7"; + + src = pkgs.fetchgit { + url = "https://github.com/philpem/printer-driver-ptouch"; + rev = "v${version}"; + hash = "sha256-3ZotSHn7lERp53hAzx47Ct/k565rEoensCcltwX/Xls="; + }; + + nativeBuildInputs = [ + pkgs.autoreconfHook + pkgs.perl + ]; + + buildInputs = [ + pkgs.cups + pkgs.libpng + pkgs.perlPackages.XMLLibXML + pkgs.foomatic-db-engine + ]; + + patches = [ + # Add this patch to have the package actually build sucessfully. + # https://github.com/philpem/printer-driver-ptouch/pull/35 + (pkgs.fetchpatch { + name = "fix-brother-ql-600.xml.patch"; + url = "https://patch-diff.githubusercontent.com/raw/philpem/printer-driver-ptouch/pull/35.patch"; + hash = "sha256-y5bHKFeRXx8Wdl1++l4QNGgiY41LY5uzrRdOlaZyF9I="; + }) + ]; + + # Used the following as a reference on how to generate the ppd files. + # https://salsa.debian.org/printing-team/ptouch-driver/-/blob/4ba5d2c490ea1230374aa4b0bf711bf77f1ab0c7/debian/rules#L34 + postInstall = '' + mkdir -p $out/share/cups + FOOMATICDB=$out/share/foomatic ${pkgs.foomatic-db-engine}/bin/foomatic-compiledb -t ppd -d $out/share/cups/model + rm -r $out/share/foomatic + ''; + + postPatch = '' + patchShebangs --build foomaticalize + ''; + }; + forcecommand-lpr-wrapper = pkgs.python3Packages.buildPythonApplication { + name = "forcecommand-lpr-wrapper"; + src = ./forcecommand-lpr-wrapper; + + propagatedBuildInputs = [ + pkgs.cups + ]; + }; +in +{ + services.printing = { + enable = true; + drivers = [ printer-driver-ptouch ]; + stateless = true; + }; + + hardware.printers = { + ensurePrinters = [ + { + name = "Brother-QL-500"; + location = "Z9"; + deviceUri = "usb://Brother/QL-500?serial=J8Z249208"; + model = "Brother-QL-500-ptouch-ql.ppd"; + ppdOptions = { + PageSize = "Custom.62x35mm"; + }; + } + ]; + ensureDefaultPrinter = "Brother-QL-500"; + }; + + users.users.print = { + isNormalUser = true; + description = "User for printing via SSH."; + password = ""; + }; + + # PasswordAuthentication being set to false just puts "auth required + # pam_deny.so # deny (order 12400)" for pam.d/sshd, so enable + # PasswordAuthentication to have it not do that. + services.openssh.settings.PasswordAuthentication = lib.mkForce true; + # The following doesn't need to be set in order for empty passwords to work + # apparently: + # security.pam.services.sshd.allowNullPassword = true; + services.openssh.extraConfig = '' + Match User print + PubkeyAuthentication no + AuthenticationMethods none + PermitEmptyPasswords yes + ForceCommand ${forcecommand-lpr-wrapper}/bin/forcecommand-lpr-wrapper.py + Match User * + ''; +} diff --git a/flake.nix b/flake.nix index f66397d..5360d1f 100644 --- a/flake.nix +++ b/flake.nix @@ -197,6 +197,20 @@ ./config/hosts/forgejo-actions-runner ]; }; + + ptouch-print-server = { + deployment = { + targetHost = "ptouch-print-server.z9.ccchh.net"; + targetPort = 22; + targetUser = "colmena-deploy"; + tags = [ "thinkcccluster" ]; + }; + imports = [ + ./config/common + ./config/proxmox-vm + ./config/hosts/ptouch-print-server + ]; + }; }; packages.x86_64-linux = {