text: # Copyright (c) Jupyter Development Team.
  # Distributed under the terms of the Modified BSD License.
  # Major edits by MathematicalMichael(.com) 02-2019
  
  # Configuration file for JupyterHub
  import os
  import sys
  from subprocess import check_call
  pwd = os.path.dirname(__file__)
  c = get_config()
  
  
  ### Helper scripts
  def create_group_map(filename='userlist') -> 'Dict[str, List[str]]':
      """
      Parses text file to assign users to groups and creates
      a dictionary where keys are usernames and values are the list
      of group names to which they belong.
  
      You can use group membership to define policies for shared volumes
      or use an `admin` group to determine which users get root permissions.
  
      Note that updates to the userlist require restarts for the jupyerhub to
      take effect. This can be inconvenient as an interruption to existing users.
      
      For this reason, we suggest not using `userlist` to manage
      shared volumes but rather setting up an external filesystem on the network
      and managing access through that (give users instructions on how to mount them
      as folders inside their containerized environments), or perhaps opt for
      object storage like s3 and distribute user-keys / credentials and rely on
      CLI or programmatic file access.
      """
      group_map = {}
      # TODO: check if file exists and return empty dictionary if it does not.
      with open(os.path.join(pwd, filename)) as f:
          for line in f:
              if not line:
                  continue
              # each line of file is user: group_1 group_2 ...
              parts = line.split()
              # in case of newline at the end of userlist file
              if len(parts) == 0:
                  continue
              user_name = parts[0]
              group_map[user_name] = []
  
              for i in range(1,len(parts)):
                  group_name = parts.pop()
                  group_map[user_name].append(group_name)
      return group_map
  
  
  def create_volume_mount(group_id='group', mode='ro', nb_user='jovyan') -> 'Dict[str, Dict[str, str]]':
      volumes = {}
      volume_name = f'shared-{group_id}'
      volume_config = {
          'bind': f'/home/{nb_user}/{volume_name}',
          'mode': mode,
      }
      volumes[volume_name] = volume_config
      return volumes
  
  
  
  # We rely on environment variables to configure JupyterHub so that we
  # avoid having to rebuild the JupyterHub container every time we change a
  # configuration parameter.
  HUB_NAME = os.environ['HUB_NAME']
  DEFAULT_IMAGE = f'{HUB_NAME}-user'
  GROUP_MAP = create_group_map('userlist')
  
  # Allow admin users to log into other single-user servers (e.g. for debugging, testing)?  As a courtesy, you should make sure your users know if admin_access is enabled.
  c.JupyterHub.admin_access = True
  
  ## Allow named single-user servers per user
  c.JupyterHub.allow_named_servers = True
  
  # Allow admin to access other users' containers
  c.NotebookApp.allow_remote_access = True
  
  # Optional list of images
  ENABLE_DROPDOWN = True
  IMAGE_WHITELIST= {
      'default': f"{HUB_NAME}-user",
      'scipy-notebook': "jupyter/scipy-notebook", 
      'tensorflow-notebook': "jupyter/tensorflow-notebook",
      'r-notebook': 'jupyter/r-notebook',
      'base-notebook': "jupyter/base-notebook",
  }
  
  
  # Spawn single-user servers as Docker containers
  from dockerspawner import DockerSpawner
  class MyDockerSpawner(DockerSpawner):
      def start(self):
          group_list = GROUP_MAP.get(self.user.name, [])
          self.update_volumes(group_list)
          # if 'admin' in group_list:
          #     self.mount_config_files()
          #     self.grant_sudo()
          # self.limit_resources()
          self.enable_lab()
          self.grant_sudo()  # grants sudo to all users!!!
          return super().start()
  
      def grant_sudo(self):
          """
          Grants sudo permission to current user being spawned.
          """
          self.environment['GRANT_SUDO'] = "1"
          self.extra_create_kwargs = {'user': 'root'}
      
      def enable_lab(self):
          """
          Sets Jupyterlab as the default environment which users see.
          """
          self.environment['JUPYTER_ENABLE_LAB'] = 'yes'
          self.default_url = '/lab'
  
      def update_volumes(self, group_list):
          for group_id in group_list:
              mode = 'rw' if 'admin' in group_list else 'ro'
              volume = create_volume_mount(group_id, mode, 'jovyan') 
              self.volumes.update(volume)
  
      def limit_resources(self, mem_limit='8G', cpu_limit=1.0):
          self.mem_limit = mem_limit
          self.cpu_limit = cpu_limit
  
      def mount_config_files(self, username='jovyan'):
          """
          Allows editing of `jupyterhub_config.py` + `userlist` from
          within the container but relies on using `Shut Down` from
          the admin panel + docker automatically restarting the hub
          in order for changes to take effect. If you make a mistake,
          your hub will become unavailable and you will need to edit
          it by logging into the server hosting the jupyterhub app.
          """
          self.volumes['%s/userlist'%(os.environ['HUB_LOC'])] = \
              { 'bind': f'/home/{username}/userlist', 'mode': 'rw' }
          self.volumes['%s/jupyterhub_config.py'%(os.environ['HUB_LOC'])] = \
              { 'bind': f'/home/{username}/jupyterhub_config.py', 'mode': 'rw' }
  
  c.JupyterHub.spawner_class = MyDockerSpawner
  c.DockerSpawner.image = '%s-user'%HUB_NAME
  c.DockerSpawner.name_template = '%s-{username}-{servername}-{imagename}'%HUB_NAME
  if ENABLE_DROPDOWN:
      c.DockerSpawner.allowed_images = IMAGE_WHITELIST
  
  # JupyterHub requires a single-user instance of the Notebook server, so we
  # default to using the `start-singleuser.sh` script included in the
  # jupyter/docker-stacks *-notebook images as the Docker run command when
  # spawning containers.  Optionally, you can override the Docker run command
  # using the DOCKER_SPAWN_CMD environment variable.
  spawn_cmd = os.environ.get('DOCKER_SPAWN_CMD', "start-singleuser.sh")
  c.DockerSpawner.extra_create_kwargs.update({ 'command': spawn_cmd })
  
  # Connect containers to this Docker network
  network_name = '%s-network'%HUB_NAME
  c.DockerSpawner.use_internal_ip = True
  c.DockerSpawner.network_name = network_name
  # Pass the network name as argument to spawned containers
  c.DockerSpawner.extra_host_config = { 'network_mode': network_name }
  
  # Explicitly set notebook directory because we'll be mounting a host volume to
  # it.  Most jupyter/docker-stacks *-notebook images run the Notebook server as
  # user `jovyan`, and set the notebook directory to `/home/jovyan/work`.
  # We follow the same convention.
  notebook_dir = os.environ.get('DOCKER_NOTEBOOK_DIR') or '/home/jovyan/work'
  #c.DockerSpawner.notebook_dir = notebook_dir
  # Mount the real user's Docker volume on the host to the notebook user's
  # notebook directory in the container
  c.DockerSpawner.volumes = { 'hub-user-{username}': notebook_dir }
  
  # volume_driver is no longer a keyword argument to create_container()
  # c.DockerSpawner.extra_create_kwargs.update({ 'volume_driver': 'local' })
  
  # Remove containers once they are stopped
  c.DockerSpawner.remove = True
  
  # For debugging arguments passed to spawned containers
  c.DockerSpawner.debug = True
  
  # User containers will access hub by container name on the Docker network
  c.JupyterHub.hub_ip = HUB_NAME
  # The hub will be hosted at example.com/HUB_NAME/ 
  c.JupyterHub.base_url = u'/%s/'%HUB_NAME
  #c.JupyterHub.hub_port = 8001
  
  ## Authentication 
  # Whitlelist users and admins
  c.Authenticator.allowed_users = whitelist = set()
  c.Authenticator.admin_users = admin = set()
  
  # add default user so that first-time log in is easy.
  admin.add('hub-admin')
  for name in GROUP_MAP:
      if 'admin' in GROUP_MAP[name]:
          admin.add(name)
      else:
          whitelist.add(name)
  
  # Authenticate users with GitHub OAuth
  # c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'
  # c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
  
  # Authenticate with thedataincubator/jupyterhub-hashauthenticator
  c.JupyterHub.authenticator_class = 'hashauthenticator.HashAuthenticator'
  # You can generate a good "secret key" by running `openssl rand -hex 32` in terminal.
  # it is recommended to do this from time-to-time to change passwords (including changing their length)
  c.HashAuthenticator.secret_key = os.environ['HASH_SECRET_KEY']  # Defaults to ''
  c.HashAuthenticator.password_length = int(os.environ['PASSWORD_LENGTH'])          # Defaults to 6
  # Can find your password by looking at `hashauthpw --length 10 [username] [key]`
  # If the `show_logins` option is set to `True`, a CSV file containing 
  #login names and passwords will be served (to admins only) at `/hub/login_list`. 
  c.HashAuthenticator.show_logins = True            # Optional, defaults to False
  
  # TLS config
  #c.JupyterHub.port = 8000
  #c.JupyterHub.ssl_key = os.environ['SSL_KEY']
  #c.JupyterHub.ssl_cert = os.environ['SSL_CERT']
  
  ### Database Interaction - cookies, db for jupyterhub
  # Persist hub data on volume mounted inside container
  data_dir = '/data' # DATA_VOLUME_CONTAINER
  
  c.JupyterHub.cookie_secret_file = os.path.join(data_dir,
      'jupyterhub_cookie_secret')
  
  c.JupyterHub.db_url = 'postgresql://postgres:{password}@{host}/{db}'.format(
      host=os.environ['POSTGRES_HOST'],
      password=os.environ['POSTGRES_PASSWORD'],
      db=HUB_NAME,
  )
  
  # https://github.com/jupyterhub/jupyterhub-idle-culler
  c.JupyterHub.services = [
      {
          "name": "jupyterhub-idle-culler-service",
          "command": [
              sys.executable,
              "-m", "jupyterhub_idle_culler",
              "--timeout=3600",
          ],
          "admin": True,
      }
  ]
  