root / pykota / trunk / bin / pksetup @ 3021

Revision 3021, 18.5 kB (checked in by jerome, 18 years ago)

Added pksetup for Debian & Ubuntu

  • Property svn:executable set to *
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3#
4# PyKota
5#
6# PyKota : Print Quotas for CUPS and LPRng
7#
8# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22#
23# $Id$
24#
25#
26
27import sys
28import os
29import stat
30import tempfile
31import pwd
32import grp
33
34nowready = """
35
36
37PyKota is now ready to run !
38
39Before printing, you still have to manually modify CUPS' printers.conf
40to manually prepend cupspykota:// in front of each DeviceURI.
41
42Once this is done, just restart CUPS and all should work fine.
43
44Please report any problem to : alet@librelogiciel.com
45
46Thanks in advance.
47"""
48
49pghbaconf = """local\tall\tpostgres\t\tident sameuser
50local\tall\tall\t\tident sameuser
51host\tall\tall\t127.0.0.1\t255.255.255.255\tident sameuser
52host\tall\tall\t::1\tffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\treject
53host\tall\tall\t::ffff:127.0.0.1/128\treject
54host\tall\tall\t0.0.0.0\t0.0.0.0\treject"""
55
56pykotadminconf = """[global]
57storageadmin: pykotaadmin
58storageadminpw: readwritepw"""
59
60pykotaconf = """#
61# This is a generated configuration file for PyKota
62#
63# IMPORTANT : many more directives can be used, and some of the directives
64# below accept different and/or more complex parameters. Please read
65# /usr/share/pykota/conf/pykota.conf.sample for more details about the
66# numerous possibilities allowed.
67#
68[global]
69
70# Database settings
71storagebackend : pgstorage
72storageserver : %(storageserver)s
73storagename : pykota
74storageuser : pykotauser
75storageuserpw : readonlypw
76storagecaching : No
77disablehistory : No
78
79# Logging method
80logger : system
81
82# Set debug to Yes during installation and testing
83debug : Yes
84
85# Who should receive automatic bug reports ?
86crashrecipient : pykotacrashed@librelogiciel.com
87
88# Should we keep temporary files on disk ?
89# Set this to yes for debugging software accounting problems
90keepfiles : no
91
92# Logos for banners and CGI scripts
93logourl : http://www.pykota.com/pykota.png
94logolink : http://www.pykota.com/
95
96# SMTP
97smtpserver : %(smtpserver)s
98maildomain : %(dnsdomain)s
99
100# Print Administrator
101admin : %(adminname)s
102adminmail : %(adminemail)s
103
104# Use usernames as-is or convert them to lowercase ?
105utolower : No
106
107# Should we hide some fields in the history (title, filename) ?
108privacy : no
109
110# Should we charge end users when an error occurs ?
111onbackenderror : nocharge
112
113# Default accounting methods :
114preaccounter : software()
115accounter : software()
116onaccountererror : stop
117
118# Who will receive warning messages ?
119# both means admin and user.
120mailto : both
121
122# Grace delay for pages based quotas, works the same
123# as for disk quotas
124gracedelay : 7
125
126# Configurable zero, to give free credits
127balancezero : 0.0
128
129# Warning limit for credit based quotas
130poorman : 1.0
131
132# Warning messages to use
133poorwarn : Your Print Quota account balance is low.
134 Soon you'll not be allowed to print anymore.
135
136softwarn : Your Print Quota Soft Limit is reached.
137 This means that you may still be allowed to print for some
138 time, but you must contact your administrator to purchase
139 more print quota.
140
141hardwarn : Your Print Quota Hard Limit is reached.
142 This means that you are not allowed to print anymore.
143 Please contact your administrator at root@localhost
144 as soon as possible to solve the problem.
145
146# Number of banners allowed to be printed by users
147# who are over quota
148maxdenybanners : 0
149
150# Should we allow users to ever be over quota on their last job ?
151# strict means no.
152enforcement : strict
153
154# Should we trust printers' internal page counter ?
155trustjobsize : yes
156
157# How to handle duplicate jobs
158denyduplicates : no
159duplicatesdelay : 0
160
161# What should we do when an unknown user prints ?
162# The policy below will automatically create a printing account
163# for unknown users, allowing them to print with no limit on the
164# current printer.
165policy : external(pkusers --add --skipexisting --limitby noquota --description "Added automatically" \$PYKOTAUSERNAME && edpykota --add --skipexisting --printer \$PYKOTAPRINTERNAME \$PYKOTAUSERNAME)
166
167"""
168
169
170class PyKotaSetup :
171    """Base class for PyKota installers."""
172    backendsdirectory = "/usr/lib/cups/backend" # overload it if needed
173    pykotadirectory = "/usr/share/pykota"       # overload it if needed
174    pgrestart = "/etc/init.d/postgresql* restart" # overload it if needed
175    cupsrestart = "/etc/init.d/cupsys restart"  # overload it if needed
176    adduser = "adduser --system --group --home /etc/pykota --gecos PyKota pykota" # overload it if needed
177    packages = [ "wget", 
178                 "bzip2", 
179                 "subversion", 
180                 "postgresql", 
181                 "postgresql-client",
182                 "cupsys",
183                 "cupsys-client",
184                 "python-dev",
185                 "python-jaxml",
186                 "python-reportlab",
187                 "python-reportlab-accel",
188                 "python-psyco",
189                 "python-pygresql",
190                 "python-psyco",
191                 "python-osd",
192                 "python-egenix-mxdatetime",
193                 "python-imaging",
194                 "python-pysnmp4",
195                 "python-chardet",
196                 "python-pam" ]
197       
198    otherpackages = [ { "name" : "pkpgcounter",
199                        "version" : "2.10",
200                        "url" : "http://www.pykota.com/software/%(name)s/download/tarballs/%(name)s-%(version)s.tar.gz",
201                        "commands" : [ "tar -zxf %(name)s-%(version)s.tar.gz",
202                                       "cd %(name)s-%(version)s",
203                                       "python setup.py install",
204                                     ],
205                      },
206                      { "name" : "pkipplib",
207                        "version" : "0.07",
208                        "url" : "http://www.pykota.com/software/%(name)s/download/tarballs/%(name)s-%(version)s.tar.gz",
209                        "commands" : [ "tar -zxf %(name)s-%(version)s.tar.gz",
210                                       "cd %(name)s-%(version)s",
211                                       "python setup.py install",
212                                     ],
213                      },
214                      { "name" : "ghostpcl",
215                        "version" : "1.41",
216                        "url" : "ftp://mirror.cs.wisc.edu/pub/mirrors/ghost/AFPL/GhostPCL/%(name)s_%(version)s.tar.bz2", 
217                        "commands" : [ "bunzip2 <%(name)s_%(version)s.tar.bz2 | tar -xf -",
218                                       "cd %(name)s_%(version)s",
219                                       "make fonts",
220                                       "make product",
221                                       "make install",
222                                     ],
223                      },
224                   ] 
225                   
226    def __init__(self) :               
227        """Initializes instance specific datas."""
228        self.launched = []
229       
230    def yesno(self, message) :       
231        """Asks the end user some question and returns the answer."""
232        answer = raw_input("\n%s ? " % message).strip().upper()[0]
233        return answer == 'Y'
234       
235    def confirmCommand(self, message, command, record=True) :   
236        """Asks for confirmation before a command is launched, and launches it if needed."""
237        if self.yesno("The following command will be launched %(message)s :\n%(command)s\nDo you agree" % locals()) :
238            os.system(command)
239            if record :
240                self.launched.append(command)
241            return True
242        else :
243            return False
244           
245    def confirmPipe(self, message, command) :   
246        """Asks for confirmation before a command is launched in a pipe, launches it if needed, and returns the result."""
247        if self.yesno("The following command will be launched %(message)s :\n%(command)s\nDo you agree" % locals()) :
248            pipeprocess = os.popen(command, "r")
249            result = pipeprocess.read()
250            pipeprocess.close()
251            return result
252        else :
253            return False
254           
255    def listPrinters(self) :
256        """Returns a list of tuples (queuename, deviceuri) for all existing print queues."""
257        result = os.popen("lpstat -v", "r")
258        lines = result.readlines()
259        result.close()
260        printers = []
261        for line in lines :
262            (begin, end) = line.split(':', 1)
263            deviceuri = end.strip()
264            queuename = begin.split()[-1]
265            printers.append((queuename, deviceuri))
266        return printers   
267       
268    def downloadOtherPackages(self) :   
269        """Downloads and install additional packages from http://www.pykota.com or other websites"""
270        olddirectory = os.getcwd()
271        directory = tempfile.mkdtemp()
272        print "\nDownloading additional software not available as packages in %(directory)s" % locals()
273        os.chdir(directory)
274        for package in self.otherpackages :
275            name = package["name"]
276            version = package["version"]
277            url = package["url"] % locals()
278            commands = " && ".join(package["commands"]) % locals()
279            if url.startswith("svn://") :
280                download = 'svn export "%(url)s" %(name)s' % locals()
281            else :   
282                download = 'wget "%(url)s"' % locals()
283            if self.confirmCommand("to download %(name)s" % locals(), download) :
284                self.confirmCommand("to install %(name)s" % locals(), commands)
285        self.confirmCommand("to remove the temporary directory %(directory)s" % locals(),     
286                            "rm -fr %(directory)s" % locals(),
287                            record=False)
288        os.chdir(olddirectory)   
289       
290    def waitPrintersOnline(self) :
291        """Asks the admin to switch all printers ON."""
292        while not self.yesno("First you MUST switch ALL your printers ON. Are ALL your printers ON") :
293            pass
294           
295    def setupDatabase(self) :
296        """Creates the database."""
297        pykotadirectory = self.pykotadirectory
298        self.confirmCommand("to create PyKota's database in PostgreSQL", 'su - postgres -c "psql -f %(pykotadirectory)s/postgresql/pykota-postgresql.sql template1"' % locals())
299       
300    def configurePostgreSQL(self) :
301        """Configures PostgreSQL for PyKota to work."""
302        pgconffile = self.confirmPipe("to find PostgreSQL's configuration files", "find /etc -name postgresql.conf 2>/dev/null")
303        if pgconffile is not False :
304            pgconffile = pgconffile.strip()
305            pgconfdir = os.path.split(pgconffile)[0]
306            answer = self.confirmPipe("to see if PostgreSQL accepts TCP/IP connections", "grep ^tcpip_socket %(pgconffile)s" % locals())
307            conflines = pghbaconf.split("\n")
308            if answer is not False :
309                tcpip = answer.strip().lower().endswith("true")
310            else :   
311                tcpip = False
312            if tcpip :   
313                conflines.insert(2, "host\tpykota\tpykotaadmin,pykotauser\t127.0.0.1\t255.255.255.255\tmd5")
314            else :
315                conflines.insert(1, "local\tpykota\tpykotaadmin,pykotauser\t\tmd5")
316            conf = "\n".join(conflines)
317            self.confirmCommand("to configure PostgreSQL correctly for PyKota", 'echo "%(conf)s" >%(pgconfdir)s/pg_hba.conf' % locals())
318            self.confirmCommand("to make PostgreSQL take the changes into account", self.pgrestart)
319            return tcpip
320        return None   
321       
322    def genConfig(self, adminname, adminemail, dnsdomain, smtpserver, home, tcpip) :       
323        """Generates minimal configuration files for PyKota."""
324        if tcpip :
325            storageserver = "localhost"
326        else :   
327            storageserver = ""
328        conf = pykotaconf % locals()
329        self.confirmCommand("to generate PyKota's main configuration file", 'echo "%(conf)s" >%(home)s/pykota.conf' % locals())
330        conf = pykotadminconf % locals()
331        self.confirmCommand("to generate PyKota's administrators configuration file", 'echo "%(conf)s" >%(home)s/pykotadmin.conf' % locals())
332        self.confirmCommand("to change permissions on PyKota's administrators configuration file", "chmod 640 %(home)s/pykotadmin.conf" % locals())
333        self.confirmCommand("to change permissions on PyKota's main configuration file", "chmod 644 %(home)s/pykota.conf" % locals())
334        self.confirmCommand("to change ownership of PyKota's configuration files", "chown pykota.pykota %(home)s/pykota.conf %(home)s/pykotadmin.conf" % locals())
335        answer = self.confirmPipe("to automatically detect the best settings for your printers", "pkturnkey --doconf 2>/dev/null")
336        if answer is not False :
337            lines = answer.split("\n")
338            begin = end = None
339            for i in range(len(lines)) :
340                line = lines[i]
341                if line.strip().startswith("--- CUT ---") :
342                    if begin is None :
343                        begin = i
344                    else :   
345                        end = i
346                       
347            if (begin is not None) and (end is not None) :
348                suffix = "\n".join(lines[begin+1:end])
349                self.confirmCommand("to improve PyKota's configuration wrt your existing printers", 'echo "%(suffix)s" >>%(home)s/pykota.conf' % locals())
350       
351    def importPrinters(self) :
352        """Imports the existing print queues in the database."""
353        self.confirmCommand("to import your existing print queues into PyKota's database", "pkturnkey --force 2>/dev/null")
354           
355    def addPyKotaUser(self) :
356        """Adds a system user named pykota, returns its home directory or None"""
357        try :
358            user = pwd.getpwnam("pykota")
359        except KeyError :   
360            if self.confirmCommand("to create a system user named 'pykota'", self.adduser) :
361                try :   
362                    return pwd.getpwnam("pykota")[5]
363                except KeyError :   
364                    return None
365            else :       
366                return None
367        else :   
368            return user[5]
369   
370    def setupBackend(self) :
371        """Installs the cupspykota backend."""
372        backendsdirectory = self.backendsdirectory
373        pykotadirectory = self.pykotadirectory
374        self.confirmCommand("to make PyKota known to CUPS", "ln -s %(pykotadirectory)s/cupspykota %(backendsdirectory)s/cupspykota" % locals())
375        self.confirmCommand("to restart CUPS for the changes to take effect", self.cupsrestart)
376       
377    def managePrinters(self, printers) :   
378        """For each printer, asks if it should be managed with PyKota or not."""
379        for (queuename, deviceuri) in printers :
380            if not deviceuri.startswith("cupspykota:") :
381                newdeviceuri = "cupspykota://%s" % deviceuri
382                command = "lpadmin -p %(queuename)s -v %(newdeviceuri)s" % locals()
383                self.confirmCommand("to manage the %(queuename)s print queue with PyKota" % locals(), command)
384   
385    def setup(self) :
386        """Installation procedure."""
387        self.waitPrintersOnline()
388        adminname = raw_input("What is the name of the print administrator ? ").strip()
389        adminemail = raw_input("What is the email address of the print administrator ? ").strip()
390        dnsdomain = raw_input("What is your DNS domain name ? ").strip()
391        smtpserver = raw_input("What is the hostname or IP address of your SMTP server ? ").strip()
392        homedirectory = self.addPyKotaUser()
393        if homedirectory is None :
394            sys.stderr.write("Installation can't proceed. You MUST create a system user named 'pykota'.\n")
395        else :   
396            self.upgradeSystem()
397            self.setupPackages()
398            self.downloadOtherPackages()
399            tcpip = self.configurePostgreSQL()
400            self.genConfig(adminname, adminemail, dnsdomain, smtpserver, homedirectory, tcpip)
401            self.setupDatabase()
402            self.setupBackend()
403            self.managePrinters(self.listPrinters())
404            self.importPrinters()
405            print nowready
406            print "The script %s can be used to reinstall in unattended mode.\n" % self.genInstaller()
407           
408    def genInstaller(self) :       
409        """Generates an installer script."""
410        scriptname = "/tmp/pykota-installer.sh"
411        commands = [ "#! /bin/sh", 
412                     "#",
413                     "# PyKota installer script.", 
414                     "#", 
415                     "# This script was automatically generated.",
416                     "#",
417                   ] + self.launched
418        script = open(scriptname, "w")           
419        script.write("\n".join(commands))
420        script.close()
421        os.chmod(scriptname, \
422                 stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
423        return scriptname   
424       
425       
426class Debian(PyKotaSetup) :       
427    """Class for Debian installer."""
428    def setupPackages(self) :   
429        """Installs missing Debian packages."""
430        self.confirmCommand("to install missing dependencies", "apt-get install %s" % " ".join(self.packages))
431           
432    def upgradeSystem(self) :
433        """Upgrades the Debian setup."""
434        if self.confirmCommand("to grab an up-to-date list of available packages", "apt-get update") :
435            self.confirmCommand("to put your system up-to-date", "apt-get -y dist-upgrade")
436   
437class Ubuntu(Debian) :   
438    """Class for Ubuntu installer."""
439    pass
440   
441if __name__ == "__main__" :       
442    retcode = 0
443    if len(sys.argv) != 2 :
444        sys.stderr.write("usage : pksetup distribution\ne.g. : pksetup debian\n")
445    else :   
446        classname = sys.argv[1].strip().title()
447        try :
448            installer = globals()[classname]()
449        except KeyError :   
450            sys.stderr.write("There's currently no support for the %s distribution, sorry.\n" % sys.argv[1])
451            retcode = -1
452        else :   
453            try :
454                retcode = installer.setup()
455            except KeyboardInterrupt :             
456                sys.stderr.write("\n\n\nWARNING : Setup was aborted at user's request !\n\n")
457                retcode = -1
458    sys.exit(retcode)
Note: See TracBrowser for help on using the browser.