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

Revision 3570, 18.9 kB (checked in by jerome, 11 years ago)

Removed references to psyco.

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