Mercurial > p > roundup > code
comparison roundup/scripts/roundup_server.py @ 2632:9c55f2bc5961
roundup-server now has a configuration file (-C option)
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Tue, 27 Jul 2004 00:45:49 +0000 |
| parents | 5a8d9465827e |
| children | a9e1fff1e793 |
comparison
equal
deleted
inserted
replaced
| 2631:2bbcfc80ba5b | 2632:9c55f2bc5961 |
|---|---|
| 15 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | 15 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| 16 # | 16 # |
| 17 | 17 |
| 18 """Command-line script that runs a server over roundup.cgi.client. | 18 """Command-line script that runs a server over roundup.cgi.client. |
| 19 | 19 |
| 20 $Id: roundup_server.py,v 1.56 2004-07-20 02:07:58 richard Exp $ | 20 $Id: roundup_server.py,v 1.57 2004-07-27 00:45:49 richard Exp $ |
| 21 """ | 21 """ |
| 22 __docformat__ = 'restructuredtext' | 22 __docformat__ = 'restructuredtext' |
| 23 | 23 |
| 24 # python version check | 24 # python version check |
| 25 from roundup import version_check | 25 from roundup import version_check |
| 26 from roundup import __version__ as roundup_version | 26 from roundup import __version__ as roundup_version |
| 27 | 27 |
| 28 import sys, os, urllib, StringIO, traceback, cgi, binascii, getopt, imp | 28 import sys, os, urllib, StringIO, traceback, cgi, binascii, getopt, imp |
| 29 import SocketServer, BaseHTTPServer, socket, errno | 29 import SocketServer, BaseHTTPServer, socket, errno, ConfigParser |
| 30 | 30 |
| 31 # Roundup modules of use here | 31 # Roundup modules of use here |
| 32 from roundup.cgi import cgitb, client | 32 from roundup.cgi import cgitb, client |
| 33 import roundup.instance | 33 import roundup.instance |
| 34 from roundup.i18n import _ | 34 from roundup.i18n import _ |
| 35 | 35 |
| 36 try: | 36 try: |
| 37 import signal | 37 import signal |
| 38 except: | 38 except: |
| 39 signal = None | 39 signal = None |
| 40 | |
| 41 # | |
| 42 ## Configuration | |
| 43 # | |
| 44 | |
| 45 # This indicates where the Roundup trackers live. They're given as NAME -> | |
| 46 # TRACKER_HOME, where the NAME part is used in the URL to select the | |
| 47 # appropriate reacker. | |
| 48 # Make sure the NAME part doesn't include any url-unsafe characters like | |
| 49 # spaces, as these confuse the cookie handling in browsers like IE. | |
| 50 TRACKER_HOMES = { | |
| 51 # 'example': '/path/to/example', | |
| 52 } | |
| 53 | |
| 54 ROUNDUP_USER = None | |
| 55 ROUNDUP_GROUP = None | |
| 56 ROUNDUP_LOG_IP = 1 | |
| 57 HOSTNAME = '' | |
| 58 PORT = 8080 | |
| 59 PIDFILE = None | |
| 60 LOGFILE = None | |
| 61 | |
| 62 | |
| 63 # | |
| 64 ## end configuration | |
| 65 # | |
| 66 | 40 |
| 67 # "default" favicon.ico | 41 # "default" favicon.ico |
| 68 # generate by using "icotool" and tools/base64 | 42 # generate by using "icotool" and tools/base64 |
| 69 import zlib, base64 | 43 import zlib, base64 |
| 70 favico = zlib.decompress(base64.decodestring(''' | 44 favico = zlib.decompress(base64.decodestring(''' |
| 79 bn3Zuj8M9Hepux6VfZtW1yA6K7cfGqVu8TL325u+fHTb71QKbk+7TZQ+lTc6RcnpqW8qmVQBoj/g | 53 bn3Zuj8M9Hepux6VfZtW1yA6K7cfGqVu8TL325u+fHTb71QKbk+7TZQ+lTc6RcnpqW8qmVQBoj/g |
| 80 23eo0sr/NIGvB37K+lOWXMvJ+uWFeKGU/03Cb7n3D4M3wxI= | 54 23eo0sr/NIGvB37K+lOWXMvJ+uWFeKGU/03Cb7n3D4M3wxI= |
| 81 '''.strip())) | 55 '''.strip())) |
| 82 | 56 |
| 83 class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): | 57 class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
| 84 TRACKER_HOMES = TRACKER_HOMES | 58 TRACKER_HOMES = {} |
| 85 ROUNDUP_USER = ROUNDUP_USER | 59 LOG_IPADDRESS = 1 |
| 86 | 60 |
| 87 def run_cgi(self): | 61 def run_cgi(self): |
| 88 """ Execute the CGI command. Wrap an innner call in an error | 62 """ Execute the CGI command. Wrap an innner call in an error |
| 89 handler so all errors can be caught. | 63 handler so all errors can be caught. |
| 90 """ | 64 """ |
| 214 | 188 |
| 215 # do the roundup thang | 189 # do the roundup thang |
| 216 c = tracker.Client(tracker, self, env) | 190 c = tracker.Client(tracker, self, env) |
| 217 c.main() | 191 c.main() |
| 218 | 192 |
| 219 LOG_IPADDRESS = ROUNDUP_LOG_IP | |
| 220 def address_string(self): | 193 def address_string(self): |
| 221 if self.LOG_IPADDRESS: | 194 if self.LOG_IPADDRESS: |
| 222 return self.client_address[0] | 195 return self.client_address[0] |
| 223 else: | 196 else: |
| 224 host, port = self.client_address | 197 host, port = self.client_address |
| 363 message += '\n' | 336 message += '\n' |
| 364 print _('''%(message)sUsage: roundup-server [options] [name=tracker home]* | 337 print _('''%(message)sUsage: roundup-server [options] [name=tracker home]* |
| 365 | 338 |
| 366 Options: | 339 Options: |
| 367 -v prints the Roundup version number and exits | 340 -v prints the Roundup version number and exits |
| 341 -C <fname> use configuration file | |
| 368 -n <name> sets the host name of the Roundup web server instance | 342 -n <name> sets the host name of the Roundup web server instance |
| 369 -p <port> sets the port to listen on (default: %(port)s) | 343 -p <port> sets the port to listen on (default: %(port)s) |
| 370 -l <fname> log to the file indicated by fname instead of stderr/stdout | 344 -l <fname> log to the file indicated by fname instead of stderr/stdout |
| 371 -N log client machine names instead of IP addresses (much slower) | 345 -N log client machine names instead of IP addresses (much slower) |
| 372 %(os_part)s | 346 %(os_part)s |
| 373 | 347 |
| 374 Examples: | 348 Examples: |
| 349 roundup-server -C /opt/roundup/etc/roundup-server.ini | |
| 350 | |
| 375 roundup-server support=/var/spool/roundup-trackers/support | 351 roundup-server support=/var/spool/roundup-trackers/support |
| 376 | 352 |
| 377 roundup-server -d /var/run/roundup.pid -l /var/log/roundup.log \\ | 353 roundup-server -d /var/run/roundup.pid -l /var/log/roundup.log \\ |
| 378 support=/var/spool/roundup-trackers/support | 354 support=/var/spool/roundup-trackers/support |
| 355 | |
| 356 Configuration file format: | |
| 357 See the "admin_guide" in the Roundup "doc" directory. | |
| 379 | 358 |
| 380 How to use "name=tracker home": | 359 How to use "name=tracker home": |
| 381 These arguments set the tracker home(s) to use. The name is how the | 360 These arguments set the tracker home(s) to use. The name is how the |
| 382 tracker is identified in the URL (it's the first part of the URL path). | 361 tracker is identified in the URL (it's the first part of the URL path). |
| 383 The tracker home is the directory that was identified when you did | 362 The tracker home is the directory that was identified when you did |
| 384 "roundup-admin init". You may specify any number of these name=home | 363 "roundup-admin init". You may specify any number of these name=home |
| 385 pairs on the command-line. For convenience, you may edit the | 364 pairs on the command-line. Make sure the name part doesn't include |
| 386 TRACKER_HOMES variable in the roundup-server file instead. | 365 any url-unsafe characters like spaces, as these confuse IE. |
| 387 Make sure the name part doesn't include any url-unsafe characters like | |
| 388 spaces, as these confuse the cookie handling in browsers like IE. | |
| 389 ''')%locals() | 366 ''')%locals() |
| 390 sys.exit(0) | 367 sys.exit(0) |
| 368 | |
| 391 | 369 |
| 392 def daemonize(pidfile): | 370 def daemonize(pidfile): |
| 393 ''' Turn this process into a daemon. | 371 ''' Turn this process into a daemon. |
| 394 - make sure the sys.std(in|out|err) are completely cut off | 372 - make sure the sys.std(in|out|err) are completely cut off |
| 395 - make our parent PID 1 | 373 - make our parent PID 1 |
| 422 devnull = os.open('/dev/null', 0) | 400 devnull = os.open('/dev/null', 0) |
| 423 os.dup2(devnull, 0) | 401 os.dup2(devnull, 0) |
| 424 os.dup2(devnull, 1) | 402 os.dup2(devnull, 1) |
| 425 os.dup2(devnull, 2) | 403 os.dup2(devnull, 2) |
| 426 | 404 |
| 405 def setgid(group): | |
| 406 if group is None: | |
| 407 return | |
| 408 if not hasattr(os, 'setgid'): | |
| 409 return | |
| 410 | |
| 411 # if root, setgid to the running user | |
| 412 if not os.getuid(): | |
| 413 print _('WARNING: ignoring "-g" argument, not root') | |
| 414 return | |
| 415 | |
| 416 try: | |
| 417 import grp | |
| 418 except ImportError: | |
| 419 raise ValueError, _("Can't change groups - no grp module") | |
| 420 try: | |
| 421 try: | |
| 422 gid = int(group) | |
| 423 except ValueError: | |
| 424 gid = grp.getgrnam(group)[2] | |
| 425 else: | |
| 426 grp.getgrgid(gid) | |
| 427 except KeyError: | |
| 428 raise ValueError,_("Group %(group)s doesn't exist")%locals() | |
| 429 os.setgid(gid) | |
| 430 | |
| 431 def setuid(user): | |
| 432 if not hasattr(os, 'getuid'): | |
| 433 return | |
| 434 | |
| 435 # People can remove this check if they're really determined | |
| 436 if user is None: | |
| 437 raise ValueError, _("Can't run as root!") | |
| 438 | |
| 439 if os.getuid(): | |
| 440 print _('WARNING: ignoring "-u" argument, not root') | |
| 441 | |
| 442 try: | |
| 443 import pwd | |
| 444 except ImportError: | |
| 445 raise ValueError, _("Can't change users - no pwd module") | |
| 446 try: | |
| 447 try: | |
| 448 uid = int(user) | |
| 449 except ValueError: | |
| 450 uid = pwd.getpwnam(user)[2] | |
| 451 else: | |
| 452 pwd.getpwuid(uid) | |
| 453 except KeyError: | |
| 454 raise ValueError, _("User %(user)s doesn't exist")%locals() | |
| 455 os.setuid(uid) | |
| 456 | |
| 427 def run(port=PORT, success_message=None): | 457 def run(port=PORT, success_message=None): |
| 428 ''' Script entry point - handle args and figure out what to to. | 458 ''' Script entry point - handle args and figure out what to to. |
| 429 ''' | 459 ''' |
| 430 # time out after a minute if we can | 460 # time out after a minute if we can |
| 431 import socket | 461 import socket |
| 432 if hasattr(socket, 'setdefaulttimeout'): | 462 if hasattr(socket, 'setdefaulttimeout'): |
| 433 socket.setdefaulttimeout(60) | 463 socket.setdefaulttimeout(60) |
| 434 | 464 |
| 435 hostname = HOSTNAME | 465 undefined = [] |
| 436 pidfile = PIDFILE | 466 hostname = pidfile = logfile = user = group = svc_args = log_ip = undefined |
| 437 logfile = LOGFILE | 467 config = None |
| 438 user = ROUNDUP_USER | |
| 439 group = ROUNDUP_GROUP | |
| 440 svc_args = None | |
| 441 | 468 |
| 442 try: | 469 try: |
| 443 # handle the command-line args | 470 # handle the command-line args |
| 444 options = 'n:p:g:u:d:l:hNv' | 471 options = 'n:p:g:u:d:l:C:hNv' |
| 445 if RoundupService: | 472 if RoundupService: |
| 446 options += 'c' | 473 options += 'c' |
| 447 | 474 |
| 448 try: | 475 try: |
| 449 optlist, args = getopt.getopt(sys.argv[1:], options) | 476 optlist, args = getopt.getopt(sys.argv[1:], options) |
| 450 except getopt.GetoptError, e: | 477 except getopt.GetoptError, e: |
| 451 usage(str(e)) | 478 usage(str(e)) |
| 452 | 479 |
| 453 user = ROUNDUP_USER | |
| 454 group = None | |
| 455 for (opt, arg) in optlist: | 480 for (opt, arg) in optlist: |
| 456 if opt == '-n': hostname = arg | 481 if opt == '-n': hostname = arg |
| 457 elif opt == '-v': | 482 elif opt == '-v': |
| 458 print '%s (python %s)'%(roundup_version, sys.version.split()[0]) | 483 print '%s (python %s)'%(roundup_version, sys.version.split()[0]) |
| 459 return | 484 return |
| 461 elif opt == '-u': user = arg | 486 elif opt == '-u': user = arg |
| 462 elif opt == '-g': group = arg | 487 elif opt == '-g': group = arg |
| 463 elif opt == '-d': pidfile = os.path.abspath(arg) | 488 elif opt == '-d': pidfile = os.path.abspath(arg) |
| 464 elif opt == '-l': logfile = os.path.abspath(arg) | 489 elif opt == '-l': logfile = os.path.abspath(arg) |
| 465 elif opt == '-h': usage() | 490 elif opt == '-h': usage() |
| 466 elif opt == '-N': RoundupRequestHandler.LOG_IPADDRESS = 0 | 491 elif opt == '-N': log_ip = 0 |
| 467 elif opt == '-c': svc_args = [opt] + args; args = None | 492 elif opt == '-c': svc_args = [opt] + args; args = None |
| 493 elif opt == '-C': config = arg | |
| 468 | 494 |
| 469 if svc_args is not None and len(optlist) > 1: | 495 if svc_args is not None and len(optlist) > 1: |
| 470 raise ValueError, _("windows service option must be the only one") | 496 raise ValueError, _("windows service option must be the only one") |
| 471 | 497 |
| 472 if pidfile and not logfile: | 498 if pidfile and not logfile: |
| 473 raise ValueError, _("logfile *must* be specified if pidfile is") | 499 raise ValueError, _("logfile *must* be specified if pidfile is") |
| 500 | |
| 501 # handle the config file | |
| 502 if config: | |
| 503 cfg = ConfigParser.ConfigParser() | |
| 504 cfg.read(filename) | |
| 505 if port is undefined: | |
| 506 port = cfg.get('server', 'port', 8080) | |
| 507 if user is undefined and cfg.has_option('server', 'user'): | |
| 508 user = cfg.get('server', 'user') | |
| 509 if group is undefined and cfg.has_option('server', 'group'): | |
| 510 group = cfg.get('server', 'group') | |
| 511 if log_ip is undefined and cfg.has_option('server', 'log_ip'): | |
| 512 RoundupRequestHandler.LOG_IPADDRESS = cfg.getboolean('server', | |
| 513 'log_ip') | |
| 514 if pidfile is undefined and cfg.has_option('server', 'pidfile'): | |
| 515 pidfile = cfg.get('server', 'pidfile') | |
| 516 if logfile is undefined and cfg.has_option('server', 'logfile'): | |
| 517 logfile = cfg.get('server', 'logfile') | |
| 518 homes = RoundupRequestHandler.TRACKER_HOMES | |
| 519 for section in cfg.sections(): | |
| 520 if section == 'server': | |
| 521 continue | |
| 522 homes[section] = cfg.get(section, 'home') | |
| 474 | 523 |
| 475 # obtain server before changing user id - allows to use port < | 524 # obtain server before changing user id - allows to use port < |
| 476 # 1024 if started as root | 525 # 1024 if started as root |
| 477 address = (hostname, port) | 526 address = (hostname, port) |
| 478 | |
| 479 try: | 527 try: |
| 480 httpd = server_class(address, RoundupRequestHandler) | 528 httpd = server_class(address, RoundupRequestHandler) |
| 481 except socket.error, e: | 529 except socket.error, e: |
| 482 if e[0] == errno.EADDRINUSE: | 530 if e[0] == errno.EADDRINUSE: |
| 483 raise socket.error, \ | 531 raise socket.error, \ |
| 484 _("Unable to bind to port %s, port already in use." % port) | 532 _("Unable to bind to port %s, port already in use."%port) |
| 485 raise | 533 raise |
| 486 | 534 |
| 487 if group is not None and hasattr(os, 'getuid'): | 535 # change user and/or group |
| 488 # if root, setgid to the running user | 536 setgid(group) |
| 489 if not os.getuid(): | 537 setuid(user) |
| 490 try: | |
| 491 import grp | |
| 492 except ImportError: | |
| 493 raise ValueError, _("Can't change groups - no grp module") | |
| 494 try: | |
| 495 try: | |
| 496 gid = int(group) | |
| 497 except ValueError: | |
| 498 gid = grp.getgrnam(group)[2] | |
| 499 else: | |
| 500 grp.getgrgid(gid) | |
| 501 except KeyError: | |
| 502 raise ValueError,_("Group %(group)s doesn't exist")%locals() | |
| 503 os.setgid(gid) | |
| 504 elif os.getuid(): | |
| 505 print _('WARNING: ignoring "-g" argument, not root') | |
| 506 | |
| 507 if hasattr(os, 'getuid'): | |
| 508 # if root, setuid to the running user | |
| 509 if not os.getuid() and user is not None: | |
| 510 try: | |
| 511 import pwd | |
| 512 except ImportError: | |
| 513 raise ValueError, _("Can't change users - no pwd module") | |
| 514 try: | |
| 515 try: | |
| 516 uid = int(user) | |
| 517 except ValueError: | |
| 518 uid = pwd.getpwnam(user)[2] | |
| 519 else: | |
| 520 pwd.getpwuid(uid) | |
| 521 except KeyError: | |
| 522 raise ValueError, _("User %(user)s doesn't exist")%locals() | |
| 523 os.setuid(uid) | |
| 524 elif os.getuid() and user is not None: | |
| 525 print _('WARNING: ignoring "-u" argument, not root') | |
| 526 | |
| 527 # People can remove this check if they're really determined | |
| 528 if not os.getuid() and user is None: | |
| 529 raise ValueError, _("Can't run as root!") | |
| 530 | 538 |
| 531 # handle tracker specs | 539 # handle tracker specs |
| 532 if args: | 540 if args: |
| 533 d = {} | |
| 534 for arg in args: | 541 for arg in args: |
| 535 try: | 542 try: |
| 536 name, home = arg.split('=') | 543 name, home = arg.split('=') |
| 537 except ValueError: | 544 except ValueError: |
| 538 raise ValueError, _("Instances must be name=home") | 545 raise ValueError, _("Instances must be name=home") |
| 539 d[name] = os.path.abspath(home) | 546 home = os.path.abspath(home) |
| 540 RoundupRequestHandler.TRACKER_HOMES = d | 547 RoundupRequestHandler.TRACKER_HOMES[name] = home |
| 541 except SystemExit: | 548 except SystemExit: |
| 542 raise | 549 raise |
| 543 except ValueError: | 550 except ValueError: |
| 544 usage(error()) | 551 usage(error()) |
| 545 except: | 552 except: |
| 547 sys.exit(1) | 554 sys.exit(1) |
| 548 | 555 |
| 549 # we don't want the cgi module interpreting the command-line args ;) | 556 # we don't want the cgi module interpreting the command-line args ;) |
| 550 sys.argv = sys.argv[:1] | 557 sys.argv = sys.argv[:1] |
| 551 | 558 |
| 559 # fork the server from our parent if a pidfile is specified | |
| 552 if pidfile: | 560 if pidfile: |
| 553 if not hasattr(os, 'fork'): | 561 if not hasattr(os, 'fork'): |
| 554 print _("Sorry, you can't run the server as a daemon" | 562 print _("Sorry, you can't run the server as a daemon" |
| 555 " on this Operating System") | 563 " on this Operating System") |
| 556 sys.exit(0) | 564 sys.exit(0) |
