Ticket #693: trac_693-spawn_notebook.3.patch

File trac_693-spawn_notebook.3.patch, 16.4 KB (added by mpatel, 12 years ago)

Open browser if server running and open_viewer=True. pep8 clean-ups. Rebased for queue in comment. Replaces previous.

  • sagenb/notebook/run_notebook.py

    # HG changeset patch
    # User Mitesh Patel <qed777@gmail.com>
    # Date 1264400671 28800
    # Node ID 995849992345a900950d4723862c25bbea34f919
    # Parent  1a05bb7bd4d8a2a47e8993974482d590e29b29ba
    #693/sagenb: Open a browser if a server is already running and open_viewer=True
    
    diff --git a/sagenb/notebook/run_notebook.py b/sagenb/notebook/run_notebook.py
    a b except ImportError: 
    2121    protocol = 'ssl'
    2222
    2323# System libraries
    24 import getpass, os, shutil, socket, sys
     24import getpass
     25import os
     26import shutil
     27import socket
     28import sys
    2529from exceptions import SystemExit
    2630
    2731from twisted.python.runtime import platformType
    2832
    29 from sagenb.misc.misc import (DOT_SAGENB, print_open_msg, find_next_available_port)
    30 
     33from sagenb.misc.misc import (DOT_SAGENB, find_next_available_port,
     34                              print_open_msg)
    3135import notebook
    3236
    33 conf_path       = os.path.join(DOT_SAGENB, 'notebook')
     37conf_path     = os.path.join(DOT_SAGENB, 'notebook')
    3438
    3539private_pem   = os.path.join(conf_path, 'private.pem')
    3640public_pem    = os.path.join(conf_path, 'public.pem')
    3741template_file = os.path.join(conf_path, 'cert.cfg')
    3842
     43
    3944def cmd_exists(cmd):
    4045    """
    4146    Return True if the given cmd exists.
    4247    """
    43     return os.system('which %s 2>/dev/null >/dev/null'%cmd) == 0
     48    return os.system('which %s 2>/dev/null >/dev/null' % cmd) == 0
     49
     50
     51def get_old_settings(conf):
     52    """
     53    Returns three settings from the Twisted configuration file conf:
     54    the interface, port number, and whether the server is secure.  If
     55    there are any errors, this returns (None, None, None).
     56    """
     57    import re
     58    # This should match the format written to twistedconf.tac below.
     59    p = re.compile(r'interface="(.*)",port=(\d*),secure=(True|False)')
     60    try:
     61        interface, port, secure = p.search(open(conf, 'r').read()).groups()
     62        if secure == 'True':
     63            secure = True
     64        else:
     65            secure = False
     66        return interface, port, secure
     67    except IOError, AttributeError:
     68        return None, None, None
     69
    4470
    4571def notebook_setup(self=None):
    4672    if not os.path.exists(conf_path):
    4773        os.makedirs(conf_path)
    4874
    4975    if not cmd_exists('certtool'):
    50         raise RuntimeError, "You must install certtool to use the secure notebook server."
     76        raise RuntimeError("You must install certtool to use the secure notebook server.")
    5177
    5278    dn = raw_input("Domain name [localhost]: ").strip()
    5379    if dn == '':
    def notebook_setup(self=None): 
    6389                'cn': dn,
    6490                'uid': 'sage_user',
    6591                'dn_oid': None,
    66                 'serial': str(random.randint(1,2**31)),
     92                'serial': str(random.randint(1, 2 ** 31)),
    6793                'dns_name': None,
    6894                'crl_dist_points': None,
    6995                'ip_address': None,
    def notebook_setup(self=None): 
    75101                'signing_key': True,
    76102                'encryption_key': True,
    77103                }
    78                
     104
    79105    s = ""
    80106    for key, val in template_dict.iteritems():
    81107        if val is None:
    def notebook_setup(self=None): 
    86112            w = ' '.join(['"%s"' % x for x in val])
    87113        else:
    88114            w = '"%s"' % val
    89         s += '%s = %s \n' % (key, w) 
    90    
     115        s += '%s = %s \n' % (key, w)
     116
    91117    f = open(template_file, 'w')
    92118    f.write(s)
    93119    f.close()
    94120
    95121    import subprocess
    96    
     122
    97123    if os.uname()[0] != 'Darwin' and cmd_exists('openssl'):
    98124        # We use openssl by default if it exists, since it is open
    99125        # *vastly* faster on Linux, for some weird reason.
    def notebook_setup(self=None): 
    102128        print cmd[0]
    103129        subprocess.call(cmd, shell=True)
    104130    else:
    105         # We checked above that certtool is available. 
     131        # We checked above that certtool is available.
    106132        cmd = ['certtool --generate-privkey --outfile %s' % private_pem]
    107133        print "Using certtool to generate key"
    108134        print cmd[0]
    109135        subprocess.call(cmd, shell=True)
    110        
    111     cmd = ['certtool --generate-self-signed --template %s --load-privkey %s \
    112            --outfile %s' % (template_file, private_pem, public_pem)]
     136
     137    cmd = ['certtool --generate-self-signed --template %s --load-privkey %s '
     138           '--outfile %s' % (template_file, private_pem, public_pem)]
    113139    print cmd[0]
    114140    subprocess.call(cmd, shell=True)
    115    
     141
    116142    # Set permissions on private cert
    117143    os.chmod(private_pem, 0600)
    118144
    119145    print "Successfully configured notebook."
    120146
    121147def notebook_twisted(self,
    122              directory   = None,
    123              port        = 8000,
    124              interface   = 'localhost',       
    125              address     = None,
    126              port_tries  = 50,
    127              secure      = False,
    128              reset       = False,
    129              accounts    = False,
    130              require_login = True,
    131                      
    132              server_pool = None,
    133              ulimit      = '',
     148             directory     = None,
     149             port          = 8000,
     150             interface     = 'localhost',
     151             address       = None,
     152             port_tries    = 50,
     153             secure        = False,
     154             reset         = False,
     155             accounts      = False,
     156             require_login = True,
    134157
    135              timeout     = 0,
     158             server_pool   = None,
     159             ulimit        = '',
    136160
    137              open_viewer = True,
     161             timeout       = 0,
    138162
    139              sagetex_path = "",
    140              start_path = "",
    141              fork = False,
    142              quiet = False,
     163             open_viewer   = True,
    143164
    144              subnets = None):
     165             sagetex_path  = "",
     166             start_path    = "",
     167             fork          = False,
     168             quiet         = False,
     169
     170             subnets       = None):
    145171    cwd = os.getcwd()
    146172    # For backwards compatible, we still allow the address to be set
    147173    # instead of the interface argument
    def notebook_twisted(self, 
    150176        message = "Use 'interface' instead of 'address' when calling notebook(...)."
    151177        warn(message, DeprecationWarning, stacklevel=3)
    152178        interface = address
    153              
     179
    154180    if directory is None:
    155         directory = '%s/sage_notebook'%DOT_SAGENB
     181        directory = '%s/sage_notebook' % DOT_SAGENB
    156182    else:
    157         if isinstance(directory, basestring) and len(directory) > 0 and directory[-1] == "/":
     183        if (isinstance(directory, basestring) and len(directory) > 0 and
     184            directory[-1] == "/"):
    158185            directory = directory[:-1]
    159            
     186
    160187    # First change to the directory that contains the notebook directory
    161188    wd = os.path.split(directory)
    162     if wd[0]: os.chdir(wd[0])
     189    if wd[0]:
     190        os.chdir(wd[0])
    163191    directory = wd[1]
    164192
    165193    port = int(port)
    166194
    167195    if not secure and interface != 'localhost':
    168         print '*'*70
     196        print '*' * 70
    169197        print "WARNING: Running the notebook insecurely not on localhost is dangerous"
    170198        print "because its possible for people to sniff passwords and gain access to"
    171199        print "your account. Make sure you know what you are doing."
    172         print '*'*70
     200        print '*' * 70
    173201
    174202    nb = notebook.load_notebook(directory)
    175    
     203
    176204    directory = nb._dir
    177205    conf = os.path.join(directory, 'twistedconf.tac')
    178    
     206
    179207    if not quiet:
    180208        print "The notebook files are stored in:", nb._dir
    181209
    182210    nb.conf()['idle_timeout'] = int(timeout)
    183    
     211
    184212    if nb.user_exists('root') and not nb.user_exists('admin'):
    185213        # This is here only for backward compatibility with one
    186         # version of the notebook. 
     214        # version of the notebook.
    187215        s = nb.create_user_with_same_password('admin', 'root')
    188         # It would be a security risk to leave an escalated account around. 
     216        # It would be a security risk to leave an escalated account around.
    189217
    190218    if not nb.user_exists('admin'):
    191219        reset = True
    192        
    193     if reset: 
    194         passwd = get_admin_passwd()               
     220
     221    if reset:
     222        passwd = get_admin_passwd()
    195223        if reset:
    196224            nb.user('admin').set_password(passwd)
    197225            print "Password changed for user 'admin'."
    def notebook_twisted(self, 
    199227            nb.create_default_users(passwd)
    200228            print "User admin created with the password you specified."
    201229            print "\n\n"
    202             print "*"*70
     230            print "*" * 70
    203231            print "\n"
    204232            if secure:
    205233                print "Login to the Sage notebook as admin with the password you specified above."
    206         #nb.del_user('root') 
    207            
     234        #nb.del_user('root')
     235
    208236    nb.set_server_pool(server_pool)
    209237    nb.set_ulimit(ulimit)
    210238    nb.set_accounts(accounts)
    211    
    212     if os.path.exists('%s/nb-older-backup.sobj'%directory):
     239
     240    if os.path.exists('%s/nb-older-backup.sobj' % directory):
    213241        nb._migrate_worksheets()
    214         os.unlink('%s/nb-older-backup.sobj'%directory)
     242        os.unlink('%s/nb-older-backup.sobj' % directory)
    215243        print "Updating to new format complete."
    216244
    217245    nb.save()
    218246    del nb
    219247
    220248    def run(port, subnets):
     249        # Is a server already running? Check if a Twistd PID exists in
     250        # the given directory.
     251        pidfile = os.path.join(directory, 'twistd.pid')
     252        if platformType != 'win32':
     253            from twisted.scripts._twistd_unix import checkPID
     254            try:
     255                checkPID(pidfile)
     256            except SystemExit as e:
     257                pid = int(open(pidfile).read())
     258
     259                if str(e).startswith('Another twistd server is running,'):
     260                    print 'Another Sage Notebook server is running, PID %d.' % pid
     261                    old_interface, old_port, old_secure = get_old_settings(conf)
     262                    if open_viewer and old_port:
     263                        old_interface = old_interface or 'localhost'
     264
     265                        print 'Opening web browser at http%s://%s:%s/ ...' % (
     266                            's' if old_secure else '', old_interface, old_port)
     267
     268                        from sagenb.misc.misc import open_page as browse_to
     269                        browse_to(old_interface, old_port, old_secure, '/')
     270                        return
     271                    print '\nPlease either stop the old server or run the new server in a different directory.'
     272                    return
     273
    221274        ## Create the config file
    222275        if secure:
    223             if not os.path.exists(private_pem) or not os.path.exists(public_pem):
     276            if (not os.path.exists(private_pem) or
     277                not os.path.exists(public_pem)):
    224278                print "In order to use an SECURE encrypted notebook, you must first run notebook.setup()."
    225279                print "Now running notebook.setup()"
    226280                notebook_setup()
    227             if not os.path.exists(private_pem) or not os.path.exists(public_pem):
     281            if (not os.path.exists(private_pem) or
     282                not os.path.exists(public_pem)):
    228283                print "Failed to setup notebook.  Please try notebook.setup() again manually."
    229             strport = '%s:%s:interface=%s:privateKey=%s:certKey=%s'%(protocol, port, interface, private_pem, public_pem)
     284            strport = '%s:%s:interface=%s:privateKey=%s:certKey=%s' % (
     285                protocol, port, interface, private_pem, public_pem)
    230286        else:
    231             strport = 'tcp:%s:interface=%s'%(port, interface)
     287            strport = 'tcp:%s:interface=%s' % (port, interface)
    232288
    233         notebook_opts = '"%s",interface="%s",port=%s,secure=%s' % (os.path.abspath(directory),
    234                 interface, port, secure)
     289        notebook_opts = '"%s",interface="%s",port=%s,secure=%s' % (
     290            os.path.abspath(directory), interface, port, secure)
    235291
    236292        if open_viewer:
    237293            if require_login:
    def notebook_twisted(self, 
    242298                hostname = interface
    243299            else:
    244300                hostname = 'localhost'
    245             open_page = "from sagenb.misc.misc import open_page; open_page('%s', %s, %s, %s)"%(hostname, port, secure, start_path)
     301            open_page = "from sagenb.misc.misc import open_page; open_page('%s', %s, %s, %s)" % (hostname, port, secure, start_path)
    246302        else:
    247303            open_page = ''
    248        
     304
    249305        config = open(conf, 'w')
    250306
    251307        if subnets is None:
    class RestrictedIPFactory(channel.HTTPFa 
    267323            if a in X:
    268324                return channel.HTTPFactory.buildProtocol(self, addr)
    269325        print 'Ignoring all requests from IP address '+str(addr.host)
    270        
     326
    271327factory = RestrictedIPFactory(site)
    272 """%tuple([subnets])
     328""" % tuple([subnets])
    273329
    274330        config.write("""
    275 ####################################################################       
     331####################################################################
    276332# WARNING -- Do not edit this file!   It is autogenerated each time
    277333# the notebook(...) command is executed.
    278334####################################################################
    def save_notebook(): 
    300356    print "Saving notebook..."
    301357    twist.notebook.save()
    302358    print "Notebook cleanly saved."
    303    
     359
    304360def my_sigint(x, n):
    305361    try:
    306362        reactor.stop()
    307363    except ReactorNotRunning:
    308364        pass
    309365    signal.signal(signal.SIGINT, signal.SIG_DFL)
    310    
    311    
     366
     367
    312368signal.signal(signal.SIGINT, my_sigint)
    313369
    314370## Disable client-side certificate request for gnutls
    s.setServiceParent(application) 
    349405
    350406reactor.addSystemEventTrigger('before', 'shutdown', save_notebook)
    351407
    352 """%(notebook_opts, sagetex_path, not require_login,
    353      os.path.abspath(directory), cwd, factory,
    354      strport, open_page))
     408""" % (notebook_opts, sagetex_path, not require_login,
     409       os.path.abspath(directory), cwd, factory,
     410       strport, open_page))
    355411
     412        config.close()
    356413
    357         config.close()                     
    358 
    359         pidfile = os.path.join(directory, 'twistd.pid')
    360         cmd = 'twistd --pidfile="%s" -ny "%s"' % (pidfile, os.path.join(directory, 'twistedconf.tac'))
    361 
    362         # Check if a Twistd PID exists in the given directory
    363         if platformType != 'win32':
    364             from twisted.scripts._twistd_unix import checkPID
    365             try:
    366                 checkPID(pidfile)
    367             except SystemExit as e:
    368                 pid = int(open(pidfile).read())
    369                 if str(e).startswith('Another twistd server is running,'):
    370                     sys.exit("""\
    371 Another Sage Notebook server is running, PID %d.
    372 
    373 Please either stop the old server or run the new server in a different directory.
    374 """ % pid)
    375414        ## Start up twisted
     415        cmd = 'twistd --pidfile="%s" -ny "%s"' % (pidfile, conf)
    376416        if not quiet:
    377             print_open_msg('localhost' if not interface else interface, port, secure=secure)
     417            print_open_msg('localhost' if not interface else interface,
     418                           port, secure=secure)
    378419        if secure and not quiet:
    379420            print "There is an admin account.  If you do not remember the password,"
    380421            print "quit the notebook and type notebook(reset=True)."
    Please either stop the old server or run 
    388429            raise socket.error
    389430        return True
    390431        # end of inner function run
    391                      
     432
    392433    if interface != 'localhost' and not secure:
    393             print "*"*70
     434            print "*" * 70
    394435            print "WARNING: Insecure notebook server listening on external interface."
    395436            print "Unless you are running this via ssh port forwarding, you are"
    396437            print "**crazy**!  You should run the notebook with the option secure=True."
    397             print "*"*70
     438            print "*" * 70
    398439
    399440    port = find_next_available_port(port, port_tries)
    400441    if open_viewer:
    Please either stop the old server or run 
    402443    return run(port, subnets)
    403444
    404445
    405 
    406 
    407 
    408 
    409 #######
    410 
    411 
    412446def get_admin_passwd():
    413     print "\n"*2
     447    print "\n" * 2
    414448    print "Please choose a new password for the Sage Notebook 'admin' user."
    415449    print "Do _not_ choose a stupid password, since anybody who could guess your password"
    416450    print "and connect to your machine could access or delete your files."
    417451    print "NOTE: Only the md5 hash of the password you type is stored by Sage."
    418452    print "You can change your password by typing notebook(reset=True)."
    419     print "\n"*2
     453    print "\n" * 2
    420454    while True:
    421455        passwd = getpass.getpass("Enter new password: ")
    422456        from sagenb.misc.misc import min_password_length