root / pykota / trunk / bin / pksetup @ 3257

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

Now detects the TCP port PostgreSQL is listening to : Debian makes
PostgreSQL 7.x listen to port 5432 (PostgreSQL's default) by default
and PostgreSQL 8.x listen to port 5433, to allow both versions to
coexist on the same machine. Unfortunately when only 8.x is
installed, as it should be, port 5433 is still used and PyKota
installations done through pksetup would fail at database connection
time.
Fixed pkpgcounter's version number to use the latest available at
this time (i.e. 3.30). A better method should be used to detect this
release number automatically.
Fixed paths to the latest release of GhostPCL, which is now part of
GhostPDL, itself licensed under the terms of the GNU GPL v2 (and NOT
later). I believe MegaPacks? could now include GhostPCL if we ever
want to.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
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, 2007 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" : "3.30",
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" : "ghostpdl",
215                        "version" : "1.51",
216                        "url" : "http://mirror.cs.wisc.edu/pub/mirrors/ghost/GPL/%(name)s/%(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 pcl",
220                                       "make pcl_install",
221                                     ],
222                      },
223                   ] 
224                   
225    def __init__(self) :               
226        """Initializes instance specific datas."""
227        self.launched = []
228       
229    def yesno(self, message) :       
230        """Asks the end user some question and returns the answer."""
231        try :
232            return raw_input("\n%s ? " % message).strip().upper()[0] == 'Y'
233        except IndexError :   
234            return False
235       
236    def confirmCommand(self, message, command, record=True) :   
237        """Asks for confirmation before a command is launched, and launches it if needed."""
238        if self.yesno("The following command will be launched %(message)s :\n%(command)s\nDo you agree" % locals()) :
239            os.system(command)
240            if record :
241                self.launched.append(command)
242            return True
243        else :
244            return False
245           
246    def confirmPipe(self, message, command) :   
247        """Asks for confirmation before a command is launched in a pipe, launches it if needed, and returns the result."""
248        if self.yesno("The following command will be launched %(message)s :\n%(command)s\nDo you agree" % locals()) :
249            pipeprocess = os.popen(command, "r")
250            result = pipeprocess.read()
251            pipeprocess.close()
252            return result
253        else :
254            return False
255           
256    def listPrinters(self) :
257        """Returns a list of tuples (queuename, deviceuri) for all existing print queues."""
258        result = os.popen("lpstat -v", "r")
259        lines = result.readlines()
260        result.close()
261        printers = []
262        for line in lines :
263            (begin, end) = line.split(':', 1)
264            deviceuri = end.strip()
265            queuename = begin.split()[-1]
266            printers.append((queuename, deviceuri))
267        return printers   
268       
269    def downloadOtherPackages(self) :   
270        """Downloads and install additional packages from http://www.pykota.com or other websites"""
271        olddirectory = os.getcwd()
272        directory = tempfile.mkdtemp()
273        print "\nDownloading additional software not available as packages in %(directory)s" % locals()
274        os.chdir(directory)
275        for package in self.otherpackages :
276            name = package["name"]
277            version = package["version"]
278            url = package["url"] % locals()
279            commands = " && ".join(package["commands"]) % locals()
280            if url.startswith("svn://") :
281                download = 'svn export "%(url)s" %(name)s' % locals()
282            else :   
283                download = 'wget --user-agent=pksetup "%(url)s"' % locals()
284            if self.confirmCommand("to download %(name)s" % locals(), download) :
285                self.confirmCommand("to install %(name)s" % locals(), commands)
286        self.confirmCommand("to remove the temporary directory %(directory)s" % locals(),     
287                            "rm -fr %(directory)s" % locals(),
288                            record=False)
289        os.chdir(olddirectory)   
290       
291    def waitPrintersOnline(self) :
292        """Asks the admin to switch all printers ON."""
293        while not self.yesno("First you MUST switch ALL your printers ON. Are ALL your printers ON") :
294            pass
295           
296    def setupDatabase(self) :
297        """Creates the database."""
298        pykotadirectory = self.pykotadirectory
299        self.confirmCommand("to create PyKota's database in PostgreSQL", 'su - postgres -c "psql -f %(pykotadirectory)s/postgresql/pykota-postgresql.sql template1"' % locals())
300       
301    def configurePostgreSQL(self) :
302        """Configures PostgreSQL for PyKota to work."""
303        pgconffiles = self.confirmPipe("to find PostgreSQL's configuration files", "find /etc -name postgresql.conf 2>/dev/null")
304        if pgconffiles is not False :
305            pgconffiles = [part.strip() for part in pgconffiles.split()]
306            pgconfdirs = [os.path.split(pgconffile)[0] for pgconffile in pgconffiles]
307            for i in range(len(pgconfdirs)) :
308                pgconfdir = pgconfdirs[i]
309                pgconffile = pgconffiles[i]
310                if (len(pgconfdirs) == 1) or self.yesno("Do PostgreSQL configuration files reside in %(pgconfdir)s" % locals()) :
311                    answer = self.confirmPipe("to see if PostgreSQL accepts TCP/IP connections", "grep ^tcpip_socket %(pgconffile)s" % locals())
312                    conflines = pghbaconf.split("\n")
313                    if answer is not False :
314                        tcpip = answer.strip().lower().endswith("true")
315                    else :   
316                        tcpip = False
317                    if tcpip :   
318                        conflines.insert(2, "host\tpykota\tpykotaadmin,pykotauser\t127.0.0.1\t255.255.255.255\tmd5")
319                    else :
320                        conflines.insert(1, "local\tpykota\tpykotaadmin,pykotauser\t\tmd5")
321                    conf = "\n".join(conflines)
322                    port = 5432
323                    if tcpip :
324                        answer = self.confirmPipe("to see on which TCP port PostgreSQL accepts connections", "grep ^port %(pgconffile)s" % locals())
325                        if answer is not False :
326                            try :
327                                port = int([p.strip() for p in answer.strip().split("=")][1])
328                            except (ValueError, IndexError, TypeError) :
329                                pass
330                    self.confirmCommand("to configure PostgreSQL correctly for PyKota", 'echo "%(conf)s" >%(pgconfdir)s/pg_hba.conf' % locals())
331                    self.confirmCommand("to make PostgreSQL take the changes into account", self.pgrestart)
332                    return (tcpip, port)
333        return (None, None)
334       
335    def genConfig(self, adminname, adminemail, dnsdomain, smtpserver, home, tcpip, port) :
336        """Generates minimal configuration files for PyKota."""
337        if tcpip :
338            storageserver = "localhost:%i" % port
339        else :   
340            storageserver = ""
341        conf = pykotaconf % locals()
342        self.confirmCommand("to generate PyKota's main configuration file", 'echo "%(conf)s" >%(home)s/pykota.conf' % locals())
343        conf = pykotadminconf % locals()
344        self.confirmCommand("to generate PyKota's administrators configuration file", 'echo "%(conf)s" >%(home)s/pykotadmin.conf' % locals())
345        self.confirmCommand("to change permissions on PyKota's administrators configuration file", "chmod 640 %(home)s/pykotadmin.conf" % locals())
346        self.confirmCommand("to change permissions on PyKota's main configuration file", "chmod 644 %(home)s/pykota.conf" % locals())
347        self.confirmCommand("to change ownership of PyKota's configuration files", "chown pykota.pykota %(home)s/pykota.conf %(home)s/pykotadmin.conf" % locals())
348        answer = self.confirmPipe("to automatically detect the best settings for your printers", "pkturnkey --doconf 2>/dev/null")
349        if answer is not False :
350            lines = answer.split("\n")
351            begin = end = None
352            for i in range(len(lines)) :
353                line = lines[i]
354                if line.strip().startswith("--- CUT ---") :
355                    if begin is None :
356                        begin = i
357                    else :   
358                        end = i
359                       
360            if (begin is not None) and (end is not None) :
361                suffix = "\n".join(lines[begin+1:end])
362                self.confirmCommand("to improve PyKota's configuration wrt your existing printers", 'echo "%(suffix)s" >>%(home)s/pykota.conf' % locals())
363       
364    def addPyKotaUser(self) :
365        """Adds a system user named pykota, returns its home directory or None"""
366        try :
367            user = pwd.getpwnam("pykota")
368        except KeyError :   
369            if self.confirmCommand("to create a system user named 'pykota'", self.adduser) :
370                try :   
371                    return pwd.getpwnam("pykota")[5]
372                except KeyError :   
373                    return None
374            else :       
375                return None
376        else :   
377            return user[5]
378   
379    def setupBackend(self) :
380        """Installs the cupspykota backend."""
381        backend = os.path.join(self.backendsdirectory, "cupspykota")
382        if not os.path.exists(backend) :
383            realbackend = os.path.join(self.pykotadirectory, "cupspykota")
384            self.confirmCommand("to make PyKota known to CUPS", "ln -s %(realbackend)s %(backend)s" % locals())
385            self.confirmCommand("to restart CUPS for the changes to take effect", self.cupsrestart)
386       
387    def managePrinters(self, printers) :   
388        """For each printer, asks if it should be managed with PyKota or not."""
389        for (queuename, deviceuri) in printers :
390            command = 'pkprinters --add --cups --description "Printer created with pksetup" "%(queuename)s"' % locals()
391            self.confirmCommand("to import the %(queuename)s print queue into PyKota's database and reroute it through PyKota" % locals(), command)
392   
393    def installPyKotaFiles(self) :
394        """Installs PyKota files through Python's Distutils mechanism."""
395        pksetupdir = os.path.split(os.path.abspath(sys.argv[0]))[0]
396        pykotadir = os.path.abspath(os.path.join(pksetupdir, ".."))
397        setuppy = os.path.join(pykotadir, "setup.py")
398        if os.path.exists(setuppy) :
399            self.confirmCommand("to install PyKota files on your system", "python %(setuppy)s install" % locals())
400       
401    def setup(self) :
402        """Installation procedure."""
403        self.installPyKotaFiles()
404        self.waitPrintersOnline()
405        adminname = raw_input("What is the name of the print administrator ? ").strip()
406        adminemail = raw_input("What is the email address of the print administrator ? ").strip()
407        dnsdomain = raw_input("What is your DNS domain name ? ").strip()
408        smtpserver = raw_input("What is the hostname or IP address of your SMTP server ? ").strip()
409        homedirectory = self.addPyKotaUser()
410        if homedirectory is None :
411            sys.stderr.write("Installation can't proceed. You MUST create a system user named 'pykota'.\n")
412        else :   
413            self.upgradeSystem()
414            self.setupPackages()
415            self.downloadOtherPackages()
416            (tcpip, port) = self.configurePostgreSQL()
417            self.genConfig(adminname, adminemail, dnsdomain, smtpserver, homedirectory, tcpip, port)
418            self.setupDatabase()
419            self.setupBackend()
420            self.managePrinters(self.listPrinters())
421            print nowready
422            print "The script %s can be used to reinstall in unattended mode.\n" % self.genInstaller()
423           
424    def genInstaller(self) :       
425        """Generates an installer script."""
426        scriptname = "/tmp/pykota-installer.sh"
427        commands = [ "#! /bin/sh", 
428                     "#",
429                     "# PyKota installer script.", 
430                     "#", 
431                     "# This script was automatically generated.",
432                     "#",
433                   ] + self.launched
434        script = open(scriptname, "w")           
435        script.write("\n".join(commands))
436        script.close()
437        os.chmod(scriptname, \
438                 stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
439        return scriptname   
440       
441       
442class Debian(PyKotaSetup) :       
443    """Class for Debian installer."""
444    def setupPackages(self) :   
445        """Installs missing Debian packages."""
446        self.confirmCommand("to install missing dependencies", "apt-get install %s" % " ".join(self.packages))
447           
448    def upgradeSystem(self) :
449        """Upgrades the Debian setup."""
450        if self.confirmCommand("to grab an up-to-date list of available packages", "apt-get update") :
451            self.confirmCommand("to put your system up-to-date", "apt-get -y dist-upgrade")
452   
453class Ubuntu(Debian) :   
454    """Class for Ubuntu installer."""
455    pass
456   
457if __name__ == "__main__" :       
458    retcode = 0
459    if (len(sys.argv) != 2) or (sys.argv[1] == "-h") or (sys.argv[1] == "--help") :
460        print "pksetup v0.1 (c) 2003-2007 Jerome Alet - alet@librelogiciel.com\n\nusage : pksetup distribution\n\ne.g. : pksetup debian\n\nIMPORTANT : only Debian and Ubuntu are currently supported."
461    elif (sys.argv[1] == "-v") or (sys.argv[1] == "--version") :   
462        print "0.1" # pksetup's own version number
463    else :   
464        classname = sys.argv[1].strip().title()
465        try :
466            installer = globals()[classname]()
467        except KeyError :   
468            sys.stderr.write("There's currently no support for the %s distribution, sorry.\n" % sys.argv[1])
469            retcode = -1
470        else :   
471            try :
472                retcode = installer.setup()
473            except KeyboardInterrupt :             
474                sys.stderr.write("\n\n\nWARNING : Setup was aborted at user's request !\n\n")
475                retcode = -1
476    sys.exit(retcode)
Note: See TracBrowser for help on using the browser.