root / pykota / branches / 1.26_fixes / bin / pksetup @ 3438

Revision 3412, 19.1 kB (checked in by jerome, 16 years ago)

Removed references to the deprecated 'utolower' directive. Fixes #23.

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