root / pykota / trunk / bin / pksetup @ 3571

Revision 3571, 19.8 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
[3489]2# -*- coding: utf-8 -*-
[3021]3#
[3260]4# PyKota : Print Quotas for CUPS
[3021]5#
[3561]6# (c) 2003-2013 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-osd",
196                 "python-egenix-mxdatetime",
197                 "python-imaging",
198                 "python-pysnmp4",
[3370]199                 "python-pam",
200                 "pkpgcounter" ]
[3413]201
[3370]202    otherpackages = [
[3021]203                      { "name" : "pkipplib",
204                        "version" : "0.07",
205                        "url" : "http://www.pykota.com/software/%(name)s/download/tarballs/%(name)s-%(version)s.tar.gz",
206                        "commands" : [ "tar -zxf %(name)s-%(version)s.tar.gz",
207                                       "cd %(name)s-%(version)s",
208                                       "python setup.py install",
209                                     ],
210                      },
[3257]211                      { "name" : "ghostpdl",
[3492]212                        "version" : "1.54",
[3257]213                        "url" : "http://mirror.cs.wisc.edu/pub/mirrors/ghost/GPL/%(name)s/%(name)s-%(version)s.tar.bz2",
[3492]214                        "commands" : [ "bunzip2 <%(name)s-%(version)s.tar.bz2 | tar -xf -",
215                                       "cd %(name)s-%(version)s",
216                                       "wget http://mirror.cs.wisc.edu/pub/mirrors/ghost/AFPL/GhostPCL/urwfonts-1.41.tar.bz2",
217                                       "bunzip2 <urwfonts-1.41.tar.bz2 | tar -xf -",
218                                       "mv urwfonts-1.41 urwfonts",
219                                       "make fonts",
[3257]220                                       "make pcl",
[3492]221                                       "make install",
[3021]222                                     ],
223                      },
[3413]224                   ]
225
226    def __init__(self) :
[3021]227        """Initializes instance specific datas."""
228        self.launched = []
[3413]229
230    def yesno(self, message) :
[3021]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'
[3413]234        except IndexError :
[3233]235            return False
[3413]236
237    def confirmCommand(self, message, command, record=True) :
[3021]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
[3413]246
247    def confirmPipe(self, message, command) :
[3021]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
[3413]256
[3021]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))
[3413]268        return printers
269
270    def downloadOtherPackages(self) :
[3021]271        """Downloads and install additional packages from http://www.pykota.com or other websites"""
272        olddirectory = os.getcwd()
273        directory = tempfile.mkdtemp()
[3430]274        sys.stdout.write("\nDownloading additional software not available as packages in %(directory)s\n" % locals())
[3021]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()
[3413]283            else :
[3257]284                download = 'wget --user-agent=pksetup "%(url)s"' % locals()
[3021]285            if self.confirmCommand("to download %(name)s" % locals(), download) :
286                self.confirmCommand("to install %(name)s" % locals(), commands)
[3413]287        self.confirmCommand("to remove the temporary directory %(directory)s" % locals(),
[3021]288                            "rm -fr %(directory)s" % locals(),
289                            record=False)
[3413]290        os.chdir(olddirectory)
291
[3021]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
[3413]296
[3021]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())
[3413]301
[3021]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()) :
[3505]312                    answer = self.confirmPipe("to see if PostgreSQL accepts TCP/IP connections", 'egrep "\\"^tcpip_socket|^listen_addresses\\"" %(pgconffile)s' % locals())
[3140]313                    conflines = pghbaconf.split("\n")
314                    if answer is not False :
[3290]315                        answer = answer.strip().lower()
316                        tcpip = answer.endswith("true")
317                        if tcpip is False :
318                            tcpip = answer.startswith("listen_addresses")
[3413]319                    else :
[3140]320                        tcpip = False
[3413]321                    if tcpip :
[3140]322                        conflines.insert(2, "host\tpykota\tpykotaadmin,pykotauser\t127.0.0.1\t255.255.255.255\tmd5")
323                    else :
324                        conflines.insert(1, "local\tpykota\tpykotaadmin,pykotauser\t\tmd5")
325                    conf = "\n".join(conflines)
[3257]326                    port = 5432
327                    if tcpip :
328                        answer = self.confirmPipe("to see on which TCP port PostgreSQL accepts connections", "grep ^port %(pgconffile)s" % locals())
329                        if answer is not False :
330                            try :
331                                port = int([p.strip() for p in answer.strip().split("=")][1])
332                            except (ValueError, IndexError, TypeError) :
333                                pass
[3140]334                    self.confirmCommand("to configure PostgreSQL correctly for PyKota", 'echo "%(conf)s" >%(pgconfdir)s/pg_hba.conf' % locals())
335                    self.confirmCommand("to make PostgreSQL take the changes into account", self.pgrestart)
[3257]336                    return (tcpip, port)
337        return (None, None)
[3413]338
[3257]339    def genConfig(self, adminname, adminemail, dnsdomain, smtpserver, home, tcpip, port) :
[3021]340        """Generates minimal configuration files for PyKota."""
341        if tcpip :
[3257]342            storageserver = "localhost:%i" % port
[3413]343        else :
[3021]344            storageserver = ""
345        conf = pykotaconf % locals()
346        self.confirmCommand("to generate PyKota's main configuration file", 'echo "%(conf)s" >%(home)s/pykota.conf' % locals())
347        conf = pykotadminconf % locals()
348        self.confirmCommand("to generate PyKota's administrators configuration file", 'echo "%(conf)s" >%(home)s/pykotadmin.conf' % locals())
349        self.confirmCommand("to change permissions on PyKota's administrators configuration file", "chmod 640 %(home)s/pykotadmin.conf" % locals())
350        self.confirmCommand("to change permissions on PyKota's main configuration file", "chmod 644 %(home)s/pykota.conf" % locals())
351        self.confirmCommand("to change ownership of PyKota's configuration files", "chown pykota.pykota %(home)s/pykota.conf %(home)s/pykotadmin.conf" % locals())
352        answer = self.confirmPipe("to automatically detect the best settings for your printers", "pkturnkey --doconf 2>/dev/null")
353        if answer is not False :
354            lines = answer.split("\n")
355            begin = end = None
356            for i in range(len(lines)) :
357                line = lines[i]
358                if line.strip().startswith("--- CUT ---") :
359                    if begin is None :
360                        begin = i
[3413]361                    else :
[3021]362                        end = i
[3413]363
[3021]364            if (begin is not None) and (end is not None) :
365                suffix = "\n".join(lines[begin+1:end])
366                self.confirmCommand("to improve PyKota's configuration wrt your existing printers", 'echo "%(suffix)s" >>%(home)s/pykota.conf' % locals())
[3413]367
[3021]368    def addPyKotaUser(self) :
369        """Adds a system user named pykota, returns its home directory or None"""
370        try :
371            user = pwd.getpwnam("pykota")
[3413]372        except KeyError :
[3021]373            if self.confirmCommand("to create a system user named 'pykota'", self.adduser) :
[3413]374                try :
[3021]375                    return pwd.getpwnam("pykota")[5]
[3413]376                except KeyError :
[3021]377                    return None
[3413]378            else :
[3021]379                return None
[3413]380        else :
[3021]381            return user[5]
[3413]382
[3021]383    def setupBackend(self) :
384        """Installs the cupspykota backend."""
[3236]385        backend = os.path.join(self.backendsdirectory, "cupspykota")
386        if not os.path.exists(backend) :
387            realbackend = os.path.join(self.pykotadirectory, "cupspykota")
388            self.confirmCommand("to make PyKota known to CUPS", "ln -s %(realbackend)s %(backend)s" % locals())
389            self.confirmCommand("to restart CUPS for the changes to take effect", self.cupsrestart)
[3413]390
391    def managePrinters(self, printers) :
[3021]392        """For each printer, asks if it should be managed with PyKota or not."""
393        for (queuename, deviceuri) in printers :
[3103]394            command = 'pkprinters --add --cups --description "Printer created with pksetup" "%(queuename)s"' % locals()
395            self.confirmCommand("to import the %(queuename)s print queue into PyKota's database and reroute it through PyKota" % locals(), command)
[3413]396
[3196]397    def installPyKotaFiles(self) :
398        """Installs PyKota files through Python's Distutils mechanism."""
399        pksetupdir = os.path.split(os.path.abspath(sys.argv[0]))[0]
400        pykotadir = os.path.abspath(os.path.join(pksetupdir, ".."))
401        setuppy = os.path.join(pykotadir, "setup.py")
[3200]402        if os.path.exists(setuppy) :
403            self.confirmCommand("to install PyKota files on your system", "python %(setuppy)s install" % locals())
[3413]404
[3021]405    def setup(self) :
406        """Installation procedure."""
[3196]407        self.installPyKotaFiles()
[3021]408        self.waitPrintersOnline()
409        adminname = raw_input("What is the name of the print administrator ? ").strip()
410        adminemail = raw_input("What is the email address of the print administrator ? ").strip()
411        dnsdomain = raw_input("What is your DNS domain name ? ").strip()
412        smtpserver = raw_input("What is the hostname or IP address of your SMTP server ? ").strip()
413        homedirectory = self.addPyKotaUser()
414        if homedirectory is None :
[3493]415            sys.stderr.write("Installation can't proceed. You MUST create a system user named 'pykota'.\n")
[3413]416        else :
[3021]417            self.upgradeSystem()
418            self.setupPackages()
419            self.downloadOtherPackages()
[3257]420            (tcpip, port) = self.configurePostgreSQL()
421            self.genConfig(adminname, adminemail, dnsdomain, smtpserver, homedirectory, tcpip, port)
[3021]422            self.setupDatabase()
423            self.setupBackend()
424            self.managePrinters(self.listPrinters())
[3430]425            sys.stdout.write("%s\n" % nowready)
426            sys.stdout.write("The script %s can be used to reinstall in unattended mode.\n\n" % self.genInstaller())
[3413]427
428    def genInstaller(self) :
[3021]429        """Generates an installer script."""
430        scriptname = "/tmp/pykota-installer.sh"
[3413]431        commands = [ "#! /bin/sh",
[3021]432                     "#",
[3413]433                     "# PyKota installer script.",
434                     "#",
[3021]435                     "# This script was automatically generated.",
436                     "#",
437                   ] + self.launched
[3413]438        script = open(scriptname, "w")
[3021]439        script.write("\n".join(commands))
440        script.close()
441        os.chmod(scriptname, \
442                 stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
[3413]443        return scriptname
444
445
446class Debian(PyKotaSetup) :
[3021]447    """Class for Debian installer."""
[3413]448    def setupPackages(self) :
[3021]449        """Installs missing Debian packages."""
450        self.confirmCommand("to install missing dependencies", "apt-get install %s" % " ".join(self.packages))
[3413]451
[3021]452    def upgradeSystem(self) :
453        """Upgrades the Debian setup."""
454        if self.confirmCommand("to grab an up-to-date list of available packages", "apt-get update") :
455            self.confirmCommand("to put your system up-to-date", "apt-get -y dist-upgrade")
[3413]456
457class Ubuntu(Debian) :
[3021]458    """Class for Ubuntu installer."""
459    pass
[3413]460
461if __name__ == "__main__" :
[3021]462    retcode = 0
[3085]463    if (len(sys.argv) != 2) or (sys.argv[1] == "-h") or (sys.argv[1] == "--help") :
[3430]464        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]465    elif (sys.argv[1] == "-v") or (sys.argv[1] == "--version") :
[3430]466        sys.stdout.write("0.1\n") # pksetup's own version number
[3413]467    else :
[3021]468        classname = sys.argv[1].strip().title()
469        try :
470            installer = globals()[classname]()
[3413]471        except KeyError :
[3493]472            sys.stderr.write("There's currently no support for the %s distribution, sorry.\n" % sys.argv[1])
[3021]473            retcode = -1
[3413]474        else :
[3021]475            try :
476                retcode = installer.setup()
[3413]477            except KeyboardInterrupt :
[3493]478                sys.stderr.write("\n\n\nWARNING : Setup was aborted at user's request !\n\n")
[3021]479                retcode = -1
480    sys.exit(retcode)
Note: See TracBrowser for help on using the browser.