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

Revision 3494, 18.9 kB (checked in by jerome, 15 years ago)

Backported the fix to #38.

  • 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.
[3494]18#
[3021]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
[3494]65# /usr/share/pykota/conf/pykota.conf.sample for more details about the
[3021]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
[3412]104# Use usernames as-is or convert them to lowercase or uppercase ?
105usernamecase : native
[3021]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
[3494]118# Who will receive warning messages ?
[3021]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
[3494]132# Warning messages to use
133poorwarn : Your Print Quota account balance is low.
[3021]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
[3494]138 time, but you must contact your administrator to purchase
[3021]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
[3494]146# Number of banners allowed to be printed by users
[3021]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
[3494]177    packages = [ "wget",
178                 "bzip2",
179                 "subversion",
180                 "postgresql",
[3021]181                 "postgresql-client",
[3405]182                 "pkpgcounter",
[3021]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" ]
[3494]197
[3405]198    otherpackages = [ { "name" : "pkipplib",
[3021]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                      },
[3494]206                      { "name" : "ghostpdl",
207                        "version" : "1.54",
208                        "url" : "http://mirror.cs.wisc.edu/pub/mirrors/ghost/GPL/%(name)s/%(name)s-%(version)s.tar.bz2",
209                        "commands" : [ "bunzip2 <%(name)s-%(version)s.tar.bz2 | tar -xf -",
210                                       "cd %(name)s-%(version)s",
211                                       "wget http://mirror.cs.wisc.edu/pub/mirrors/ghost/AFPL/GhostPCL/urwfonts-1.41.tar.bz2",
212                                       "bunzip2 <urwfonts-1.41.tar.bz2 | tar -xf -",
213                                       "mv urwfonts-1.41 urwfonts",
[3021]214                                       "make fonts",
[3494]215                                       "make pcl",
[3021]216                                       "make install",
217                                     ],
218                      },
[3494]219                   ]
220
221    def __init__(self) :
[3021]222        """Initializes instance specific datas."""
223        self.launched = []
[3494]224
225    def yesno(self, message) :
[3021]226        """Asks the end user some question and returns the answer."""
[3233]227        try :
228            return raw_input("\n%s ? " % message).strip().upper()[0] == 'Y'
[3494]229        except IndexError :
[3233]230            return False
[3494]231
232    def confirmCommand(self, message, command, record=True) :
[3021]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
[3494]241
242    def confirmPipe(self, message, command) :
[3021]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
[3494]251
[3021]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))
[3494]263        return printers
264
265    def downloadOtherPackages(self) :
[3021]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()
[3494]278            else :
[3021]279                download = 'wget "%(url)s"' % locals()
280            if self.confirmCommand("to download %(name)s" % locals(), download) :
281                self.confirmCommand("to install %(name)s" % locals(), commands)
[3494]282        self.confirmCommand("to remove the temporary directory %(directory)s" % locals(),
[3021]283                            "rm -fr %(directory)s" % locals(),
284                            record=False)
[3494]285        os.chdir(olddirectory)
286
[3021]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
[3494]291
[3021]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())
[3494]296
[3021]297    def configurePostgreSQL(self) :
298        """Configures PostgreSQL for PyKota to work."""
[3140]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", "grep ^tcpip_socket %(pgconffile)s" % locals())
308                    conflines = pghbaconf.split("\n")
309                    if answer is not False :
310                        tcpip = answer.strip().lower().endswith("true")
[3494]311                    else :
[3140]312                        tcpip = False
[3494]313                    if tcpip :
[3140]314                        conflines.insert(2, "host\tpykota\tpykotaadmin,pykotauser\t127.0.0.1\t255.255.255.255\tmd5")
315                    else :
316                        conflines.insert(1, "local\tpykota\tpykotaadmin,pykotauser\t\tmd5")
317                    conf = "\n".join(conflines)
318                    self.confirmCommand("to configure PostgreSQL correctly for PyKota", 'echo "%(conf)s" >%(pgconfdir)s/pg_hba.conf' % locals())
319                    self.confirmCommand("to make PostgreSQL take the changes into account", self.pgrestart)
320                    return tcpip
[3494]321        return None
322
323    def genConfig(self, adminname, adminemail, dnsdomain, smtpserver, home, tcpip) :
[3021]324        """Generates minimal configuration files for PyKota."""
325        if tcpip :
326            storageserver = "localhost"
[3494]327        else :
[3021]328            storageserver = ""
329        conf = pykotaconf % locals()
330        self.confirmCommand("to generate PyKota's main configuration file", 'echo "%(conf)s" >%(home)s/pykota.conf' % locals())
331        conf = pykotadminconf % locals()
332        self.confirmCommand("to generate PyKota's administrators configuration file", 'echo "%(conf)s" >%(home)s/pykotadmin.conf' % locals())
333        self.confirmCommand("to change permissions on PyKota's administrators configuration file", "chmod 640 %(home)s/pykotadmin.conf" % locals())
334        self.confirmCommand("to change permissions on PyKota's main configuration file", "chmod 644 %(home)s/pykota.conf" % locals())
335        self.confirmCommand("to change ownership of PyKota's configuration files", "chown pykota.pykota %(home)s/pykota.conf %(home)s/pykotadmin.conf" % locals())
336        answer = self.confirmPipe("to automatically detect the best settings for your printers", "pkturnkey --doconf 2>/dev/null")
337        if answer is not False :
338            lines = answer.split("\n")
339            begin = end = None
340            for i in range(len(lines)) :
341                line = lines[i]
342                if line.strip().startswith("--- CUT ---") :
343                    if begin is None :
344                        begin = i
[3494]345                    else :
[3021]346                        end = i
[3494]347
[3021]348            if (begin is not None) and (end is not None) :
349                suffix = "\n".join(lines[begin+1:end])
350                self.confirmCommand("to improve PyKota's configuration wrt your existing printers", 'echo "%(suffix)s" >>%(home)s/pykota.conf' % locals())
[3494]351
[3021]352    def addPyKotaUser(self) :
353        """Adds a system user named pykota, returns its home directory or None"""
354        try :
355            user = pwd.getpwnam("pykota")
[3494]356        except KeyError :
[3021]357            if self.confirmCommand("to create a system user named 'pykota'", self.adduser) :
[3494]358                try :
[3021]359                    return pwd.getpwnam("pykota")[5]
[3494]360                except KeyError :
[3021]361                    return None
[3494]362            else :
[3021]363                return None
[3494]364        else :
[3021]365            return user[5]
[3494]366
[3021]367    def setupBackend(self) :
368        """Installs the cupspykota backend."""
[3236]369        backend = os.path.join(self.backendsdirectory, "cupspykota")
370        if not os.path.exists(backend) :
371            realbackend = os.path.join(self.pykotadirectory, "cupspykota")
372            self.confirmCommand("to make PyKota known to CUPS", "ln -s %(realbackend)s %(backend)s" % locals())
373            self.confirmCommand("to restart CUPS for the changes to take effect", self.cupsrestart)
[3494]374
375    def managePrinters(self, printers) :
[3021]376        """For each printer, asks if it should be managed with PyKota or not."""
377        for (queuename, deviceuri) in printers :
[3103]378            command = 'pkprinters --add --cups --description "Printer created with pksetup" "%(queuename)s"' % locals()
379            self.confirmCommand("to import the %(queuename)s print queue into PyKota's database and reroute it through PyKota" % locals(), command)
[3494]380
[3196]381    def installPyKotaFiles(self) :
382        """Installs PyKota files through Python's Distutils mechanism."""
383        pksetupdir = os.path.split(os.path.abspath(sys.argv[0]))[0]
384        pykotadir = os.path.abspath(os.path.join(pksetupdir, ".."))
385        setuppy = os.path.join(pykotadir, "setup.py")
[3200]386        if os.path.exists(setuppy) :
387            self.confirmCommand("to install PyKota files on your system", "python %(setuppy)s install" % locals())
[3494]388
[3021]389    def setup(self) :
390        """Installation procedure."""
[3196]391        self.installPyKotaFiles()
[3021]392        self.waitPrintersOnline()
393        adminname = raw_input("What is the name of the print administrator ? ").strip()
394        adminemail = raw_input("What is the email address of the print administrator ? ").strip()
395        dnsdomain = raw_input("What is your DNS domain name ? ").strip()
396        smtpserver = raw_input("What is the hostname or IP address of your SMTP server ? ").strip()
397        homedirectory = self.addPyKotaUser()
398        if homedirectory is None :
399            sys.stderr.write("Installation can't proceed. You MUST create a system user named 'pykota'.\n")
[3494]400        else :
[3021]401            self.upgradeSystem()
402            self.setupPackages()
403            self.downloadOtherPackages()
404            tcpip = self.configurePostgreSQL()
405            self.genConfig(adminname, adminemail, dnsdomain, smtpserver, homedirectory, tcpip)
406            self.setupDatabase()
407            self.setupBackend()
408            self.managePrinters(self.listPrinters())
409            print nowready
410            print "The script %s can be used to reinstall in unattended mode.\n" % self.genInstaller()
[3494]411
412    def genInstaller(self) :
[3021]413        """Generates an installer script."""
414        scriptname = "/tmp/pykota-installer.sh"
[3494]415        commands = [ "#! /bin/sh",
[3021]416                     "#",
[3494]417                     "# PyKota installer script.",
418                     "#",
[3021]419                     "# This script was automatically generated.",
420                     "#",
421                   ] + self.launched
[3494]422        script = open(scriptname, "w")
[3021]423        script.write("\n".join(commands))
424        script.close()
425        os.chmod(scriptname, \
426                 stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
[3494]427        return scriptname
428
429
430class Debian(PyKotaSetup) :
[3021]431    """Class for Debian installer."""
[3494]432    def setupPackages(self) :
[3021]433        """Installs missing Debian packages."""
434        self.confirmCommand("to install missing dependencies", "apt-get install %s" % " ".join(self.packages))
[3494]435
[3021]436    def upgradeSystem(self) :
437        """Upgrades the Debian setup."""
438        if self.confirmCommand("to grab an up-to-date list of available packages", "apt-get update") :
439            self.confirmCommand("to put your system up-to-date", "apt-get -y dist-upgrade")
[3494]440
441class Ubuntu(Debian) :
[3021]442    """Class for Ubuntu installer."""
443    pass
[3494]444
445if __name__ == "__main__" :
[3021]446    retcode = 0
[3085]447    if (len(sys.argv) != 2) or (sys.argv[1] == "-h") or (sys.argv[1] == "--help") :
[3231]448        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."
[3494]449    elif (sys.argv[1] == "-v") or (sys.argv[1] == "--version") :
[3230]450        print "0.1" # pksetup's own version number
[3494]451    else :
[3021]452        classname = sys.argv[1].strip().title()
453        try :
454            installer = globals()[classname]()
[3494]455        except KeyError :
[3021]456            sys.stderr.write("There's currently no support for the %s distribution, sorry.\n" % sys.argv[1])
457            retcode = -1
[3494]458        else :
[3021]459            try :
460                retcode = installer.setup()
[3494]461            except KeyboardInterrupt :
[3021]462                sys.stderr.write("\n\n\nWARNING : Setup was aborted at user's request !\n\n")
463                retcode = -1
464    sys.exit(retcode)
Note: See TracBrowser for help on using the browser.