Virtual PDF Printer for our small office network - a step by step how to

Posted on Sat 22 November 2014 in WebDev & Code

Alternative title: How I got multiple cups-pdf printers on the same server. (I didn't, but postprocessing let me work around the problem).

Preamble:

I have a small business. For years we've been creating PDFs from any computer on our network through a "virtual appliance' called YAFPC ("Yet Another Free PDF Composer").

The appliance originally ran on an old PC, then on a server that ran several other virtual machines. It had a neat web interface and would allow PDF printers to be created that would appear on the network for all of our users to use. It had one printer for plain A4 paper, one for A4 paper with a letterhead background, another one for an obscure use of mine, and so on. If you printed to it, it would email you the PDF (for any user, without any extra setup needed per user). It could also put the PDFs on one of our file servers or make them available from it's built in file server.

If I remember correctly it cost £30 and ran since 2006 right through until today, November 2014. One of my best software investments!

However, Windows 8 came along and it no longer worked. Getting Windows 8 to print to it directly turned out to be impossible.  The program was not going to be updated or replaced with a new version. I managed a short term work around having windows 8 print to a samba printer queue which converted and forwarded to the YAFPC virtual appliance. There were problems, page sizes not be exact and so on but it worked in a fashion.

Roll forward to today when I've just got a new network PDF virtual printer working. It wasn't so easy to do (some 20 hours I guess) so here are my setup notes for others to follow.  The final run through of these notes had it installed and working in about an hour.

These steps assume you know quite a bit about setting up linux servers. Please feel free to use the comments to point out errors or corrections, or add more complete instructions, and I'll edit this post with the updates.  Also please suggest alternatives methods that you needed to use to meet your needs.

Overview - We are going to create:

  • a new Ubuntu based linux server as a virtual machine
  • Install CUPS, the Common Unix Printing System
  • Install CUPS-PDF, and extension that allows files to be created from the print queue
  • Create a postprocessing script that will run every time CUPS-PDF is used that will customise our PDF's and send them where we want them (to our users).

Sounds simple, right :-)

The actions are are formatted like this.
Notes are italic and begin with a #

Step 1:

You need to create a virtual appliance. My server is using KVM but you can use any flavour (VirtualBox, VMWare, Parallels, etc).  I won't go into how creating this here.

Get the latest Ubuntu server from http://www.ubuntu.com/download/server. If you want to follow along with me, I used Ubuntu Server 14.04.
My virtual machine was set with 1GB Ram, 1 Processor and 8GB disk space.
My finished system currently uses under 2GB of disk space and each new PDF has been less than 1MB

Install ubuntu 14.04 server, these are the setting I chose.
 RAM: 1GB,
 Processor: 1
 Disk: 8GB
 Package Selections: Only OpenSSH server
 Local User Name: pierredf (for this example, but you can use any regular name, including your own.)
 IP address in this example is 10.18.6.202 but yours will be different!

Step 2:
Update the virtual appliance with the latest ubuntu patches. Between downloading 14.04 and getting things working 14.04.1 had been released so my final run through included updating to this.

sudo apt-get update
sudo apt-get dist-upgrade

Step 3:
Now I install CUPS from the standard ubuntu repo. Note I've not built any of the software for this solution so the Ubuntu version is often older versions.
Installing CUPS also installed Samba automatically.

sudo apt-get install cups

# edit the cups config file
sudo vim /etc/cups/cupsd.conf
# Find then edit/add the following
ServerAdmin yourname@example.com
Listen 10.18.6.202:631
# Save the file!

service cups restart

sudo cupsctl remote-admin remote-any share-printers
# enables web interface and enables printing from any 
# address (e.g.: internet & your network) and allows 
# printers to be shared

# check the connection works - use a web browser to visit 
# the admin page. Don't move on until this works 
http://10.18.6.202:631
# replacing 10.18.6.202 with your IP!

Step 4:
Once cups is working (you can see the web interface), install CUPS-PDF

sudo apt-get install cups-pdf

#edit the cups-pdf config file
sudo vim /etc/cups/cups-pdf.conf

#Find and edit/add the following
Label 1
# this prefixes job_ to the file names which will 
# stop prints with the same title overrighting each other. 
# You can also use 'Label 2' to have a postfix, or 0 to 
# leave it unchanged. Geek fact  you get to feel smug 
# seeing how many PDFs your setup has created over time.

AnonUser pierredf
# The post processing script runs as this user so you need
# the PDFs to be created by this user. If you used a
# different user name, put that here instead

PostProcessing /usr/bin/cups_postprocess
# This should go in the postprocessing section

Step 5:
We need to create a Printer via the cups web interface.

# Using your web browser
https://10.18.6.202:631/admin
# replacing 10.18.6.202 with your IP!
# the user name is the system user name password, eg: pierredf

Add Printer
> cups-pdf Virtual Printer
>
> Name: PDF-Generator
> Description: PDF-Generator
> Location: Outputs to Vine>roots>PDF_Printer
> Share this printer = checked
>
> Make: Generic
> Model: Generic CUPS-PDF Printer (en)

# Note: My mac used the Description as the printer name, so I kept # description and printer name the same, with location the note for # users as to where they can find their pdfs.

Step 6:
Now we add 'pdftk' the PDF Tool Kit, to our ubuntu install. This program lets us easily manipulate PDFs and we'll use it to create our letterheaded paper prints.

sudo apt-get install pdftk

Step 7:
Prepare the postprocessing script that we pointed to in step 4.

sudo touch /usr/bin/cups_postprocess
sudo chmod 755 /usr/bin/cups_postprocess
#makes the script executable.

Step 8
The script wont be allowed to run yet. When called AppArmor will stop it.

sudo vim /etc/apparmor.d/usr.sbin.cupsd

# Add the end of the file /etc/apparmor.d/usr.sbin.cupsd,
# before the last }, add this line:

/usr/bin/cups_postprocess Ux
# using 'Ux' is bad, turning off AppArmor is also bad
# I read to use the 'rPx' flag but it doesn't seem to
# work (apparmor denied error)
#
# Example:
# My file looks like this.
#===============================
#  /var/log/cups/cups-pdf_log w,
#  /var/spool/cups/** r,
#  /var/spool/cups-pdf/** rw,
#  # Added postprocessing script
#  /usr/bin/cups_postprocess Ux
#}
#================================

# See blog comment from Alex, you may need a comma after Ux if you get an error.

sudo service apparmor restart

Step 9
Now add the content to the postprocessing script.

There are lots of comments in the file below explaining what is happening.  You'll need to set your own values in several places. Eg: the destination location which you'll set up in step 13.

 sudo vim /usr/bin/cups_postprocess

#!/bin/bash

FILENAME=`basename $1`
DIRNAME=`dirname $1`

# The script is called by:
# postprocessing commandline built
# (/usr/bin/cups_postprocess /var/spool/cups-pdf/ANONYMOUS/Title_of_your_print.pdf username printuser)
# username is the user running this script. Remember you changed this to be your regular system user name. If you skipped that step it will be 'nobody'
# the printuser is the username the print client gave. It's probably the user account on their pc.
# You can use these as variables in this script, $1 is the file name, $2 the username and $3 the client print user.

# I want PDF on blank pages to be called user-bl-job-name.pdf
PBLANK="$DIRNAME"/"$3"-bl-"$FILENAME"
# I want PDF letterhead pages to be called user-lh-job-name.pdf
PLET="$DIRNAME"/"$3"-lh-"$FILENAME"

# Rename the default to blank style name
mv $1 $PBLANK

# Use pdftk to set the letterhead as the page background.
pdftk $PBLANK multibackground /etc/cups/Letterhead.pdf output "$PLET"

# this file is created with 600 but it will need 660 on the other server so we can change that here
chmod 660 $PLET
# the original file had world read. No file on our server should be world readable, so fix that before the copy
chmod 660 $PBLANK
# Now we can use scp to transfer the two files onto the server
# Note - I also had to 'chmod g+s PDF_Printer' on the destination to force new files sent there
# to have the group permissions. Otherwise it arrived with user:pierredf and group:pierredf so no one
# else could open/read the PDFs

# use scp to send the new files to the remote server
scp $PBLANK user@some.other.server.com:/path/to/send/files/
scp $PLET user@some.other.server.com:/path/to/send/files/

### Things I'm still working on ###
# Use the user name to determine who to email the files too.


# IF user matches THEN try and email ELSE send to the general account

# sanity - check file size, if email greater than than Y MB
# message: did not attach file, go look on the server
# else
# message: attach files


# clean up the remote folder.
# this could be done by cron on the remote server, but it's handy
# to just do it from this script. Obviously only runs when a PDF
# is printed, but that's fine for my needs.
# Any files greater than 3 days old to be deleted
# (mtime = n * 24 hours ago)

# remote directory
ssh remoteuser@your.other.server.com 'find /path/to/send/files/* -mtime +3 -exec rm {} ;'
# local directory
find /var/spool/cups-pdf/ANONYMOUS/* -mtime +3 -exec rm {} ;

Step 10
Add the template PDF file(s).  The postprocess script adds a background to the print, which is our company letterhead.  If the template file is missing the postprocess script will fail and not tell you why!

Here is an example you can download to get you started: ExampleMultipageLetterhead

Put the template(s) in:  /etc/cups and name it Letterhead.pdf

sudo chmod 664 /etc/cups/Letterhead.pdf
sudo chown pierredf /etc/cups/Letterhead.pdf
#chown the file to your user name
sudo chgrp pierredf /etc/cups/Letterhead.pdf
#chgrp the file to your user group (same as user name was fine for me)

You'll notice this is a 2 page template. If you print a single page you'll only get the first page of the template used. If you print a multipage document the 2nd and subsequent pages will all use the 2nd page of the template.  It's like having a letterhead with a logo for your front page and continuation sheets for subsequent pages. Perhaps you'd think of it as having a cover page and different internal pages.

To do this the post processing script uses the line:
pdftk \$PBLANK multibackground /etc/cups/Letterhead.pdf output "\$PLET"
and it is the 'multibackground' command that organises this ability.
If you use a single background you could try
pdftk \$PBLANK background /etc/cups/Letterhead.pdf output "\$PLET"
instead.

Step 11
Create an SSH key for the new server. Add this key to your file server so that SCP will work without asking for a password.

# Log into remote server first
ssh remoteuser@your.other.server.com
# you should be asked for a password. You should be asked
# to confirm the remote server RSA fingerprint to confirm
# it's authenticity.

ssh-keygen -t rsa
# Accept defaults, don't set password

ssh-copy-id remoteuser@your.other.server.com
# Use your password for the other server. This will copy
# the new key to the other server.

# see if it works
ssh remoteuser@your.other.server.com
# you should connect to the remote server without
# needing a password.
exit

Step 12
Add your new network printer to your client operating system as you would any other network printer.

# Some tips: Get your printer url
# from http://10.18.6.202:631/printers/
# where 10.18.6.202 is replaced by your servers IP, then
# click on your new printer's link. The url of the page
# is the printer's address you can use when adding a printer.
# Example - mine is:
# https://10.18.6.202:631/printers/PDF-Generator

These settings worked for me:

Windows 8
----------
Open "Advanced Printer Setup"
Choose "The printer that I wanted isn't listed"
Select a shared printer by name
Enter your printer url, eg:
    https://10.18.6.202:631/printers/PDF-Generator

For the printer model
Vendor = Microsoft
Driver = PS Class

Windows 7
---------
Vendor = Generic
Driver = MS Publisher Colour

Mac OS X
--------
Settings > Printers & Scanners > '+' key to add new printer
Printer appeared on the list, set itself up automatically.

Step 13
Create a directory on your remote server to receive the files

#On your remote server
mkdir /someplace/PDF_Printer
chmod 775 /someplace/PDF_Printer
chown youruser PDF_Printer
chgrp yourgroup PDF_Printer
chmod g+s PDF_Printer
#this forces new files in the directory to have the group settings.

Step 14
Print!

You should see:
the PDF's generated in /var/spool/cups-pdf/ANONYMOUS/
and also see them on your remote file server

Step 15
Remove old PDF's from the local and remote directory.

NB: The following is already included in the script at step 9

# clean up the remote folder.
# this could be done by cron on the remote server, but it's handy
# to just do it from this script. Obviously only runs when a PDF
# is printed, but that's fine for my needs.
# Any files greater than 3 days old to be deleted
# (mtime = n * 24 hours ago)

# remote directory
ssh remoteuser@your.other.server.com 'find /path/to/send/files/* -mtime +3 -exec rm {} ;'
# local directory
find /var/spool/cups-pdf/ANONYMOUS/* -mtime +3 -exec rm {} ;

Step 16
Email the PDFs

Fortunately, my users are happy getting their PDFs from the shared folder.  The PDF prints arrive almost instantly and the folder is tidied up automatically so everyone is happy.  I didn't have to work the detail out here so if you do, please let me know and we can share your setup to help others.  Setting up email is easy but not trivial in todays world.  The steps you'll need to work out are:

  • Setting up a MTA (mail transfer agent - I suggest Exim4)
  • Adding SPF and DKIM to your DNS settings and server, or set up an email account/method of relay through a server that has this setup (otherwise you'll have delivery issues in the future)
  • Add to the script above code that will attach the files to an email, providing the attachments are less than X? Mb in size (I'd suggest X? is less than 10Mb, but that will depend on your users email accounts). If they are larger than that, just let the user know where they can get their PDF from (probably the shared folder)
  • Delete the file once the email is sent
  • Make sure any bounces get reported to you

~~I'm going  to add to the script to allow for emailing the files and cleaning up the remote directory~~.   ~~I plan to have this updated before the end of December 2014, so if you're reading this and I haven't updated it - let me know!~~

Solving bugs

Problem: Letterhead background isn't showing but PDF size has increased as if it is there.
Solution: Pages printed from a web page (test chrome & firefox) cannot have 'background' added by pdftk.  I also found the same behaviour from the CAD design software we use in our business.  pdftk does add the background but the printing page has a white background, not transparent, so the letterhead doesn't show through.  Print a word/writer document and see if it works. If you frequently need to print from web pages with background, try using pdftk's 'stamp' instead. This overlays your letterhead on top of the printing document, so as long as your letterhead has transparency, this will work.

Problem: PDF appears in /var/spool/cups-pdf/ANONYMOUS but nothing else happens.
Solution: In cups-pdf.conf set "LogType 4". The logs will show the command that is calling the post processing script. You can run this command yourself (it specifies the file that has been created).  If might fail and tell you why.
After that, try running each part of the script (eg the line; pdftk.... then scp.... etc) separately to see which bit is stopping. I had a number of problems I solved with this approach, including lots of file write/read permission issues.