Mercurial > p > roundup > code
comparison roundup/scripts/roundup_server.py @ 2771:d385f6c1d4ed
let config handle command line options.
support long options (names are same as in config file settings)
and config file creation from command line.
WARNING! config layout is changed:
- main options are in 'main' section (was: 'server' section);
- 'log_ip' changed to 'log_hostnames' with reverse meaning
to conform to '-N' command line option.
- trackers are all defined in section 'trackers',
with option name being tracker name
(was: separate sections per tracker).
| author | Alexander Smishlajev <a1s@users.sourceforge.net> |
|---|---|
| date | Sun, 17 Oct 2004 17:54:48 +0000 |
| parents | ea18be87d68d |
| children | 52460ff89e10 |
comparison
equal
deleted
inserted
replaced
| 2770:cdf6787ffeda | 2771:d385f6c1d4ed |
|---|---|
| 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.62 2004-09-21 09:29:18 a1s Exp $ | 20 $Id: roundup_server.py,v 1.63 2004-10-17 17:54:48 a1s 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 configuration, 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, ConfigParser | 29 import SocketServer, BaseHTTPServer, socket, errno, ConfigParser |
| 30 | 30 |
| 367 The tracker home is the directory that was identified when you did | 367 The tracker home is the directory that was identified when you did |
| 368 "roundup-admin init". You may specify any number of these name=home | 368 "roundup-admin init". You may specify any number of these name=home |
| 369 pairs on the command-line. Make sure the name part doesn't include | 369 pairs on the command-line. Make sure the name part doesn't include |
| 370 any url-unsafe characters like spaces, as these confuse IE. | 370 any url-unsafe characters like spaces, as these confuse IE. |
| 371 ''')%locals() | 371 ''')%locals() |
| 372 sys.exit(0) | 372 #sys.exit(0) |
| 373 | 373 |
| 374 | 374 |
| 375 def daemonize(pidfile): | 375 def daemonize(pidfile): |
| 376 ''' Turn this process into a daemon. | 376 ''' Turn this process into a daemon. |
| 377 - make sure the sys.std(in|out|err) are completely cut off | 377 - make sure the sys.std(in|out|err) are completely cut off |
| 459 pwd.getpwuid(uid) | 459 pwd.getpwuid(uid) |
| 460 except KeyError: | 460 except KeyError: |
| 461 raise ValueError, _("User %(user)s doesn't exist")%locals() | 461 raise ValueError, _("User %(user)s doesn't exist")%locals() |
| 462 os.setuid(uid) | 462 os.setuid(uid) |
| 463 | 463 |
| 464 class TrackerHomeOption(configuration.FilePathOption): | |
| 465 # Tracker homes do not need description strings | |
| 466 # attached to FilePathOption. Description appears once | |
| 467 # before the trackers section. | |
| 468 class_description = "" | |
| 469 | |
| 470 class ServerConfig(configuration.Config): | |
| 471 | |
| 472 SETTINGS = ( | |
| 473 ("main", ( | |
| 474 (configuration.Option, "host", "", | |
| 475 "Host name of the Roundup web server instance.\n" | |
| 476 "If empty, listen on all network interfaces."), | |
| 477 (configuration.IntegerNumberOption, "port", DEFAULT_PORT, | |
| 478 "Port to listen on."), | |
| 479 (configuration.NullableOption, "user", "", | |
| 480 "User ID as which the server will answer requests.\n" | |
| 481 "In order to use this option, " | |
| 482 "the server must be run initially as root.\n" | |
| 483 "Availability: Unix."), | |
| 484 (configuration.NullableOption, "group", "", | |
| 485 "Group ID as which the server will answer requests.\n" | |
| 486 "In order to use this option, " | |
| 487 "the server must be run initially as root.\n" | |
| 488 "Availability: Unix."), | |
| 489 (configuration.BooleanOption, "log_hostnames", "no", | |
| 490 "Log client machine names instead of IP addresses " | |
| 491 "(much slower)"), | |
| 492 (configuration.NullableFilePathOption, "pidfile", "", | |
| 493 "File to which the server records " | |
| 494 "the process id of the daemon.\n" | |
| 495 "If this option is not set, " | |
| 496 "the server will run in foreground\n"), | |
| 497 (configuration.NullableFilePathOption, "logfile", "", | |
| 498 "Log file path. If unset, log to stderr."), | |
| 499 )), | |
| 500 ("trackers", (), "Roundup trackers to serve.\n" | |
| 501 "Each option in this section defines single Roundup tracker.\n" | |
| 502 "Option name identifies the tracker and will appear in the URL.\n" | |
| 503 "Option value is tracker home directory path.\n" | |
| 504 "The path may be either absolute or relative\n" | |
| 505 "to the directory containig this config file."), | |
| 506 ) | |
| 507 | |
| 508 def __init__(self, config_file=None): | |
| 509 configuration.Config.__init__(self, config_file, self.SETTINGS) | |
| 510 | |
| 511 def _adjust_options(self, config): | |
| 512 """Add options for tracker homes""" | |
| 513 # return early if there are no tracker definitions. | |
| 514 # trackers must be specified on the command line. | |
| 515 if not config.has_section("trackers"): | |
| 516 return | |
| 517 # config defaults appear in all sections. | |
| 518 # filter them out. | |
| 519 defaults = config.defaults().keys() | |
| 520 for name in config.options("trackers"): | |
| 521 if name not in defaults: | |
| 522 self.add_option(TrackerHomeOption(self, "trackers", name)) | |
| 523 | |
| 524 def _get_name(self): | |
| 525 return "Roundup server" | |
| 526 | |
| 527 def trackers(self): | |
| 528 """Return tracker definitions as a list of (name, home) pairs""" | |
| 529 trackers = [] | |
| 530 for option in self._get_section_options("trackers"): | |
| 531 trackers.append((option, self["TRACKERS_" + option.upper()])) | |
| 532 return trackers | |
| 533 | |
| 464 undefined = [] | 534 undefined = [] |
| 465 def run(port=undefined, success_message=None): | 535 def run(port=undefined, success_message=None): |
| 466 ''' Script entry point - handle args and figure out what to to. | 536 ''' Script entry point - handle args and figure out what to to. |
| 467 ''' | 537 ''' |
| 468 # time out after a minute if we can | 538 # time out after a minute if we can |
| 469 import socket | 539 import socket |
| 470 if hasattr(socket, 'setdefaulttimeout'): | 540 if hasattr(socket, 'setdefaulttimeout'): |
| 471 socket.setdefaulttimeout(60) | 541 socket.setdefaulttimeout(60) |
| 472 | 542 |
| 473 hostname = pidfile = logfile = user = group = svc_args = log_ip = undefined | 543 config = ServerConfig() |
| 474 config = None | 544 |
| 475 | 545 options = "hvS" |
| 546 if RoundupService: | |
| 547 options += 'c' | |
| 476 try: | 548 try: |
| 477 # handle the command-line args | 549 (optlist, args) = config.getopt(sys.argv[1:], |
| 478 options = 'n:p:g:u:d:l:C:hNv' | 550 options, ("help", "version", "save-config",), |
| 479 if RoundupService: | 551 host="n:", port="p:", group="g:", user="u:", |
| 480 options += 'c' | 552 logfile="l:", pidfile="d:", log_hostnames="N") |
| 481 | 553 except (getopt.GetoptError), e: #, configuration.ConfigurationError), e: |
| 482 try: | 554 usage(str(e)) |
| 483 optlist, args = getopt.getopt(sys.argv[1:], options) | 555 return |
| 484 except getopt.GetoptError, e: | 556 |
| 485 usage(str(e)) | 557 # if running in windows service mode, don't do any other stuff |
| 486 | 558 if ("-c", "") in optlist: |
| 559 RoundupService.address = (config.HOST, config.PORT) | |
| 560 # XXX why the 1st argument to the service is "-c" | |
| 561 # instead of the script name??? | |
| 562 return win32serviceutil.HandleCommandLine(RoundupService, | |
| 563 argv=["-c"] + args) | |
| 564 | |
| 565 # add tracker names from command line. | |
| 566 # this is done early to let '--save-config' handle the trackers. | |
| 567 if args: | |
| 568 for arg in args: | |
| 569 try: | |
| 570 name, home = arg.split('=') | |
| 571 except ValueError: | |
| 572 raise ValueError, _("Instances must be name=home") | |
| 573 config.add_option( | |
| 574 configuration.FilePathOption(config, "trackers", name)) | |
| 575 config["TRACKERS_" + name.upper()] = home | |
| 576 | |
| 577 # handle remaining options | |
| 578 if optlist: | |
| 487 for (opt, arg) in optlist: | 579 for (opt, arg) in optlist: |
| 488 if opt == '-n': hostname = arg | 580 if opt in ("-h", "--help"): |
| 489 elif opt == '-v': | 581 usage() |
| 490 print '%s (python %s)'%(roundup_version, sys.version.split()[0]) | 582 elif opt in ("-v", "--version"): |
| 491 return | 583 print '%s (python %s)' % (roundup_version, |
| 492 elif opt == '-p': port = int(arg) | 584 sys.version.split()[0]) |
| 493 elif opt == '-u': user = arg | 585 elif opt in ("-S", "--save-config"): |
| 494 elif opt == '-g': group = arg | 586 config.save() |
| 495 elif opt == '-d': pidfile = os.path.abspath(arg) | 587 print _("Configuration saved to %s") % config.filepath |
| 496 elif opt == '-l': logfile = os.path.abspath(arg) | 588 # any of the above options prevent server from running |
| 497 elif opt == '-h': usage() | 589 return |
| 498 elif opt == '-N': log_ip = 0 | 590 |
| 499 elif opt == '-c': svc_args = [opt] + args; args = None | 591 RoundupRequestHandler.LOG_IPADDRESS = not config.LOG_HOSTNAMES |
| 500 elif opt == '-C': config = arg | 592 |
| 501 | 593 # obtain server before changing user id - allows to use port < |
| 502 if svc_args and len(optlist) > 1: | 594 # 1024 if started as root |
| 503 raise ValueError, _("windows service option must be the only one") | 595 try: |
| 504 | 596 httpd = server_class((config.HOST, config.PORT), RoundupRequestHandler) |
| 505 if pidfile and not logfile: | 597 except socket.error, e: |
| 506 raise ValueError, _("logfile *must* be specified if pidfile is") | 598 if e[0] == errno.EADDRINUSE: |
| 507 | 599 raise socket.error, \ |
| 508 # handle the config file | 600 _("Unable to bind to port %s, port already in use.") \ |
| 509 if config: | 601 % config.PORT |
| 510 cfg = ConfigParser.ConfigParser() | |
| 511 cfg.read(filename) | |
| 512 if port is undefined and cfg.has_option('server', 'port'): | |
| 513 port = cfg.get('server', 'port') | |
| 514 if user is undefined and cfg.has_option('server', 'user'): | |
| 515 user = cfg.get('server', 'user') | |
| 516 if group is undefined and cfg.has_option('server', 'group'): | |
| 517 group = cfg.get('server', 'group') | |
| 518 if log_ip is undefined and cfg.has_option('server', 'log_ip'): | |
| 519 RoundupRequestHandler.LOG_IPADDRESS = cfg.getboolean('server', | |
| 520 'log_ip') | |
| 521 if pidfile is undefined and cfg.has_option('server', 'pidfile'): | |
| 522 pidfile = cfg.get('server', 'pidfile') | |
| 523 if logfile is undefined and cfg.has_option('server', 'logfile'): | |
| 524 logfile = cfg.get('server', 'logfile') | |
| 525 homes = RoundupRequestHandler.TRACKER_HOMES | |
| 526 for section in cfg.sections(): | |
| 527 if section == 'server': | |
| 528 continue | |
| 529 homes[section] = cfg.get(section, 'home') | |
| 530 | |
| 531 # defaults | |
| 532 if hostname is undefined: | |
| 533 hostname = '' | |
| 534 if port is undefined: | |
| 535 port = DEFAULT_PORT | |
| 536 if group is undefined: | |
| 537 group = None | |
| 538 if user is undefined: | |
| 539 user = None | |
| 540 if svc_args is undefined: | |
| 541 svc_args = None | |
| 542 | |
| 543 # obtain server before changing user id - allows to use port < | |
| 544 # 1024 if started as root | |
| 545 address = (hostname, port) | |
| 546 try: | |
| 547 httpd = server_class(address, RoundupRequestHandler) | |
| 548 except socket.error, e: | |
| 549 if e[0] == errno.EADDRINUSE: | |
| 550 raise socket.error, \ | |
| 551 _("Unable to bind to port %s, port already in use."%port) | |
| 552 raise | |
| 553 | |
| 554 # change user and/or group | |
| 555 setgid(group) | |
| 556 setuid(user) | |
| 557 | |
| 558 # handle tracker specs | |
| 559 if args: | |
| 560 for arg in args: | |
| 561 try: | |
| 562 name, home = arg.split('=') | |
| 563 except ValueError: | |
| 564 raise ValueError, _("Instances must be name=home") | |
| 565 home = os.path.abspath(home) | |
| 566 RoundupRequestHandler.TRACKER_HOMES[name] = home | |
| 567 except SystemExit: | |
| 568 raise | 602 raise |
| 569 except ValueError: | 603 |
| 570 usage(error()) | 604 # change user and/or group |
| 571 # except: | 605 setgid(config.GROUP) |
| 572 # print error() | 606 setuid(config.USER) |
| 573 # sys.exit(1) | 607 |
| 608 # apply tracker specs | |
| 609 for (name, home) in config.trackers(): | |
| 610 home = os.path.abspath(home) | |
| 611 RoundupRequestHandler.TRACKER_HOMES[name] = home | |
| 574 | 612 |
| 575 # we don't want the cgi module interpreting the command-line args ;) | 613 # we don't want the cgi module interpreting the command-line args ;) |
| 576 sys.argv = sys.argv[:1] | 614 sys.argv = sys.argv[:1] |
| 577 | 615 |
| 578 # fork the server from our parent if a pidfile is specified | 616 # fork the server from our parent if a pidfile is specified |
| 579 if pidfile: | 617 if config.PIDFILE: |
| 580 if not hasattr(os, 'fork'): | 618 if not hasattr(os, 'fork'): |
| 581 print _("Sorry, you can't run the server as a daemon" | 619 print _("Sorry, you can't run the server as a daemon" |
| 582 " on this Operating System") | 620 " on this Operating System") |
| 583 sys.exit(0) | 621 sys.exit(0) |
| 584 else: | 622 else: |
| 585 daemonize(pidfile) | 623 daemonize(config.PIDFILE) |
| 586 | |
| 587 if svc_args is not None: | |
| 588 # don't do any other stuff | |
| 589 RoundupService.address = address | |
| 590 return win32serviceutil.HandleCommandLine(RoundupService, argv=svc_args) | |
| 591 | 624 |
| 592 # redirect stdout/stderr to our logfile | 625 # redirect stdout/stderr to our logfile |
| 593 if logfile: | 626 if config.LOGFILE: |
| 594 # appending, unbuffered | 627 # appending, unbuffered |
| 595 sys.stdout = sys.stderr = open(logfile, 'a', 0) | 628 sys.stdout = sys.stderr = open(config.LOGFILE, 'a', 0) |
| 596 | 629 |
| 597 if success_message: | 630 if success_message: |
| 598 print success_message | 631 print success_message |
| 599 else: | 632 else: |
| 600 print _('Roundup server started on %(address)s')%locals() | 633 print _('Roundup server started on %(HOST)s:%(PORT)s') \ |
| 634 % config | |
| 601 | 635 |
| 602 try: | 636 try: |
| 603 httpd.serve_forever() | 637 httpd.serve_forever() |
| 604 except KeyboardInterrupt: | 638 except KeyboardInterrupt: |
| 605 print 'Keyboard Interrupt: exiting' | 639 print 'Keyboard Interrupt: exiting' |
