root / pykota / trunk / bin / pksetup @ 3236

Revision 3236, 19.5 kB (checked in by jerome, 17 years ago)

Doesn't create a symlink to cupspykota if this is not needed.

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