root / pykota / trunk / bin / pksetup @ 3430

Revision 3430, 19.5 kB (checked in by jerome, 15 years ago)

Another round of removing print statements.

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