#!/usr/bin/env sos-runner #fileformat=SOS1.0 [global] # List of tutorial to launch # see https://hub.docker.com/u/statisticalgenetics/ for a list of options parameter: tutorial = [] import getpass parameter: my_name = getpass.getuser() parameter: version = '1.0.0' import socket, random def is_port_in_use(port): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex(('localhost', port)) == 0 def get_ports(num): all_ports = list(range(1001, 8888)) random.shuffle(all_ports) ports = [] for idx, item in enumerate(all_ports): if not is_port_in_use(item): ports.append(item) if len(ports) >= num: break return ports [build] parameter: root_dir = path('./') # version tag, default to `latest` parameter: tag = 'latest' input: for_each = 'tutorial', concurrent = False bash: expand = True, workdir = root_dir git pull docker build --build-arg DUMMY=`date +%s` -t statisticalgenetics/{_tutorial}:{tag} -f docker/{_tutorial}.dockerfile docker && \ (docker push statisticalgenetics/{_tutorial}:{tag} || true) [launch_1] output: "~/.cache/cull_idle_servers.py", "~/.cache/jupyterhub_config.py" bash: expand = True docker pull gaow/base-notebook:{version} mkdir -p $HOME/.cache curl -fsSL https://raw.githubusercontent.com/statgenetics/statgen-courses/master/src/cull_idle_servers.py -o $HOME/.cache/cull_idle_servers.py curl -fsSL https://raw.githubusercontent.com/statgenetics/statgen-courses/master/src/jupyterhub_config.py -o $HOME/.cache/jupyterhub_config.py [launch_2] fail_if(len(tutorial) == 0, msg = 'Please specify a list of tutorial to launch, via ``--tutorial`` option.') fail_if(len(my_name) == 0, msg = 'Please assign yourself a username, via ``--my-name`` option.') fail_if(not all((a.isalnum() or a == '_') and not a.isspace() for a in my_name), msg = 'Please use only alphanumeric letters and no space for ``--my-name`` input.') import os home = os.path.expanduser("~").replace('/', '\\/') input: for_each = 'tutorial', concurrent = False docker_build: tag = f'{_tutorial}_{my_name}', expand = '$[ ]', workdir = '~/.cache' FROM gaow/base-notebook:$[version] USER root RUN mkdir -p /srv/jupyterhub/ COPY jupyterhub_config.py /srv/jupyterhub/jupyterhub_config.py COPY cull_idle_servers.py /srv/jupyterhub/cull_idle_servers.py # modify the script to create a unique instance RUN sed -i "s/CONTAINER_NAME/$[_tutorial]_$[my_name]/g; s/IMAGE_NAME/statisticalgenetics\/$[_tutorial]/g; s/HOST_DIR/$[home]\/$[_tutorial]_$[my_name]/g" /srv/jupyterhub/jupyterhub_config.py EXPOSE 8000 WORKDIR /srv/jupyterhub/ LABEL org.jupyter.service="jupyterhub" CMD ["jupyterhub"] [launch_3] ports = get_ports(len(tutorial)) #ip = get_output("hostname -I | awk '{print $1}'").strip() ip = get_output("curl -so - http://checkip.amazonaws.com/").strip() # input: for_each = 'tutorial', concurrent = False bash: expand = True, workdir = '~/' set -e # Get the relevant image docker pull statisticalgenetics/{_tutorial} # Stop currently running servers to start from scratch if they are the same name; but don't stop any running instances (line below commented out) # docker container stop $(docker container ls -q --filter ancestor=statisticalgenetics/{_tutorial}) &> /dev/null || true docker container stop $(docker container ls -q --filter name={_tutorial}_{my_name}) &> /dev/null || true # Create a user folder for notebook instance and set the permission mkdir -p $HOME/{_tutorial}_{my_name} # Start jupyterhub docker network inspect {_tutorial}_{my_name} &>/dev/null || docker network create {_tutorial}_{my_name} docker run --rm -it -d -v /var/run/docker.sock:/var/run/docker.sock --net {_tutorial}_{my_name} --name {_tutorial}_{my_name} -p{ports[_index]}:8000 {_tutorial}_{my_name} # Create a shortcut to access from browser (mkdir -p /var/www/html/{my_name}/{_tutorial} && echo '' 2> /dev/null 1> /var/www/html/{my_name}/{_tutorial}/index.html && printf '\nDone! Users can now access {_tutorial} tutorial at http://{ip}/{my_name}/{_tutorial}\n\n') || true [login] # use --restart to false restart the container parameter: restart = False parameter: gemini_data_dir = path('/opt/annotation_db') fail_if(len(tutorial) != 1, msg = 'Please specify a tutorial to launch, via ``--tutorial`` option.') fail_if(len(my_name) == 0, msg = 'Please assign yourself a username, via ``--my-name`` option.') fail_if(not all((a.isalnum() or a == '_') and not a.isspace() for a in my_name), msg = 'Please use only alphanumeric letters and no space for ``--my-name`` input.') tutorial = tutorial[0] port = ip = None if tutorial == 'igv': port = get_ports(1)[0] # ip = get_output("hostname -I | awk '{print $1}'").strip() ip = get_output("curl -so - http://checkip.amazonaws.com/").strip() extra_docker_args = '' if tutorial == 'gemini' and gemini_data_dir.is_dir(): extra_docker_args = f'-v {gemini_data_dir:a}:/home/jovyan/annotation_db' bash: expand = '$[ ]', workdir = '~/' if [ "$[tutorial]" == "igv" ] then # Stop currently running instances to start from scratch docker stop sandbox_$[tutorial]_$[my_name] &> /dev/null || true # Run it in the background, `-d` option docker run -d --rm --security-opt label:disable -t --name sandbox_$[tutorial]_$[my_name] -h $[tutorial] -p $[port]:8080 statisticalgenetics/$[tutorial] > /dev/null printf '\nPlease access the IGV server at http://$[ip]:$[port]' printf '\nor, if you run this server locally (on your computer), at http://0.0.0.0:$[port]\n\n' else mkdir -p $HOME/$[tutorial]_$[my_name] && chmod g=u $HOME/$[tutorial]_$[my_name] cmd="docker exec -it sandbox_$[tutorial]_$[my_name] bash" echo "INFO: Logging in via '$cmd'" echo "INFO: '~/work' folder is mounted to '$HOME/$[tutorial]_$[my_name]' on your machine" # Stop currently running instances to start a new container if [ "$[restart]" == "True" ] then docker stop sandbox_$[tutorial]_$[my_name] &> /dev/null || true fi { $cmd 2> /dev/null } || { # Run it in the background, `-d` option docker run -d --rm --security-opt label:disable -v $HOME/$[tutorial]_$[my_name]:/home/jovyan/work -v /tmp:/tmp $[extra_docker_args] -t --name sandbox_$[tutorial]_$[my_name] -h $[tutorial] statisticalgenetics/$[tutorial] > /dev/null # Then login echo "Started a new container at `date`" $cmd } fi [update] # version tag, default to `latest` parameter: tag = 'latest' input: for_each = 'tutorial', concurrent = False bash: expand = True docker pull statisticalgenetics/{_tutorial}:{tag} [useradd] fail_if(len(my_name) == 0 or my_name == getpass.getuser(), msg = 'Please assign a username prefix, via ``--my-name`` option.') parameter: num_users = 1 passwd = [random.randint(10000, 99999) for i in range(num_users)] input: for_each = 'passwd', concurrent = False bash: expand = True set -e if [ `id -u {my_name}_{_index+1} 2>/dev/null || echo -1` -ge 0 ]; then userdel -r {my_name}_{_index+1} 2>/dev/null fi # Group `users` have group ID 100 which agrees with group ID in our docker images useradd -g users {my_name}_{_index+1} -d /home/{my_name}_{_index+1} -m -s /bin/bash # Add group `docker` to the user usermod -aG docker {my_name}_{_index+1} # Use `-m` for chpasswd option to avoid PAM's check for simple passwords echo "{my_name}_{_index+1}:{my_name}_{_passwd}" | chpasswd -m printf "New user created: {my_name}_{_index+1}:{my_name}_{_passwd}\n" [clean] import os fail_if(not os.geteuid() == 0, msg = 'This command is only meant for root user (eg, use ``sudo`` to run it)') bash: docker stop $(docker ps -a -q) || true docker rm $(docker ps -a -q) || true docker rmi $(docker images -f "dangling=true" -q) || true # install gemini database to server [gemini-db] parameter: gemini_data_dir = path('/opt/annotation_db') bash: expand = True mkdir -p {gemini_data_dir:a} bash: container='statisticalgenetics/gemini', volumes = [f"{gemini_data_dir:a}:/home/jovyan/annotation_db"] source activate py2 \ && gemini update --dataonly --extra cadd_score \ && gemini update --dataonly --extra gerp_bp # gemini update command is flawed, without running the line below the downloaded data will not be usable. mv annotation_db/gemini/data/* annotation_db/ && rm -rf annotation_db/gemini