root / pykota / trunk / bin / pksetup @ 3294

Revision 3294, 20.0 kB (checked in by jerome, 16 years ago)

Added modules to store utility functions and application
intialization code, which has nothing to do in classes.
Modified tool.py accordingly (far from being finished)
Use these new modules where necessary.
Now converts all command line arguments to unicode before
beginning to work. Added a proper logging method for already
encoded query strings.

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