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)

Roundup Issue Tracker: http://roundup-tracker.org/