Skip to content

Instantly share code, notes, and snippets.

@christianp
Last active July 27, 2016 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christianp/4fafdfc00981a69dd9bacee080ef3882 to your computer and use it in GitHub Desktop.
Save christianp/4fafdfc00981a69dd9bacee080ef3882 to your computer and use it in GitHub Desktop.

These instructions will create a virtual machine in Vagrant, which runs the Numbas LTI provider proxied through nginx. If you want to run on a real machine, skip the first couple of steps to do with Vagrant. I used Ubuntu 16.04 - the process of installing packages will be different on different versions or different distributions.

I set up Vagrant to forward port 443 on the host machine to port 443 on the VM. I think this might only work on Windows. I'm not sure how to get it running on a different port - I had problems with the nginx proxy.

Create a directory for the vagrant VM:

mkdir numbas_lti
cd numbas_lti

Edit Vagrantfile:

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/xenial64"

  config.vm.network :forwarded_port, host: 443, guest: 443
end

Start vagrant and ssh into it:

vagrant up
vagrant ssh

Install packages and set up users:

sudo addgroup numbas_lti
sudo adduser ubuntu numbas_lti
sudo adduser ubuntu www-data
sudo apt-get install nginx redis-server postgresql postgresql-server-dev-9.5 libxml2-dev libxslt1-dev python-dev lib32z1-dev python3-pip supervisor git
sudo pip3 install virtualenv

sudo git clone https://github.com/numbas/numbas-lti-provider.git /srv/numbas-lti-provider
sudo chown -R root:numbas_lti /srv/numbas-lti-provider

sudo mkdir /srv/numbas-lti-media
sudo mkdir /srv/numbas-lti-static
sudo chown -R root:numbas_lti /srv/numbas-lti-media
sudo chown -R www-data:www-data /srv/numbas-lti-static
sudo chmod -R 770 /srv/numbas-lti-*

sudo virtualenv /opt/numbas_lti_python
sudo chown -R root:numbas_lti /opt/numbas_lti_python
sudo chmod -R 770 /opt/numbas_lti_python
source /opt/numbas_lti_python/bin/activate
cd /srv/numbas-lti-provider
pip install -r requirements.txt
pip install asgi_redis psycopg2

(create postgres database numbas_lti and user numbas_lti)

sudo -i -u postgres
createuser numbas_lti -W
createdb numbas_lti
exit

Install letsencrypt to obtain an SSL certificate, or create a self-signed certificate.

In /etc/nginx/nginx.conf, add before the end of the http block:

        # connection upgrade
        map $http_upgrade $connection_upgrade {
                default upgrade;
                '' close;
        }

Edit /etc/nginx/sites-available/default

(change the ssl_certificate paths if you didn't make a self-signed certificate)

server {
    listen 443 ssl;
    listen [::]:443 ssl default_server;
    client_max_body_size 20M;

    ssl_certificate /etc/ssl/certs/server.crt;
    ssl_certificate_key /etc/ssl/private/server.key;

    root /srv/numbas-lti-static;

    # Add index.php to the list if you are using PHP
    index index.html index.htm index.nginx-debian.html;

    server_name _;

    location /static/ {
        alias /srv/numbas-lti-static/;
    }
    
    location /websocket/ {
        proxy_pass http://0.0.0.0:8707;
        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    location / {
        proxy_pass http://0.0.0.0:8707;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 1;
        proxy_send_timeout 30;
        proxy_read_timeout 30;
        proxy_redirect http:// $scheme://;
    }
}

Reload nginx:

sudo service nginx restart

Write /srv/numbas-lti-provider/pretendlti/settings.py:

(edit the database settings as appropriate)

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'qox=@g2o^ajt)uki^s84c0kk_ljnqcsx6km44209oae)lfhgn+'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'numbas_lti',
    'bootstrapform',
]

MIDDLEWARE_CLASSES = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django_auth_lti.middleware_patched.MultiLTILaunchAuthMiddleware',
    'numbas_lti.middleware.NumbasLTIResourceMiddleware',
]
AUTHENTICATION_BACKENDS = ['numbas_lti.backends.LTIAuthBackend']

ROOT_URLCONF = 'pretendlti.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'pretendlti.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'numbas_lti',
        'USER': 'numbas_lti',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',
    }
}


# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/

LANGUAGE_CODE = 'en-us'
LOCALE_PATHS = (os.path.join(BASE_DIR,'locale'),)

TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/

MEDIA_ROOT = '/srv/numbas-lti-media'
MEDIA_URL = '/media/'

STATIC_ROOT = '/srv/numbas-lti-static'
STATIC_URL = 'https://localhost/static/'

# Channels

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "asgi_redis.RedisChannelLayer",
        "CONFIG": {
            "hosts": [os.environ.get('REDIS_URL','redis://localhost:6379')],
        },
        "ROUTING": "pretendlti.routing.channel_routing",
    },
}

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO','https')

Write /srv/numbas-lti-provider/pretendlti/asgi.py:

import os
import channels.asgi

os.environ.setdefault('DJANGO_SETTINGS_MODLE','pretendlti.settings')
channel_layer = channels.asgi.get_channel_layer()

Set up database:

python manage.py migrate
python manage.py collectstatic --noinput
sudo chown -R www-data:www-data /srv/numbas-lti-static/

Set up an LTI consumer key

> python manage.py shell
from numbas_lti.models import LTIConsumer
LTIConsumer.objects.create(key='cp',secret='cp')

Edit /srv/numbas-lti-provider/start_daphne.sh

#!/bin/bash
echo "STARTING"
source /opt/numbas_lti_python/bin/activate
cd /srv/numbas-lti-provider
export DJANGO_SETTINGS_MODULE='pretendlti.settings'
daphne pretendlti.asgi:channel_layer --port 8707 --bind 0.0.0.0 -v 2

Edit /srv/numbas-lti-provider/start_worker.sh

#!/bin/bash
source /opt/numbas_lti_python/bin/activate
cd /srv/numbas-lti-provider
export DJANGO_SETTINGS_MODULE='pretendlti.settings'
python manage.py runworker

Make start_ files executable:

chmod +x /srv/numbas-lti-provider/start_*.sh

Edit /etc/supervisor/conf.d/numbas_lti.conf:

[program:numbas_lti_daphne]
command=/srv/numbas-lti-provider/start_daphne.sh
autostart=true
autorestart=true

[program:numbas_lti_worker_a]
command=/srv/numbas-lti-provider/start_worker.sh
autostart=true
autorestart=true

[program:numbas_lti_worker_b]
command=/srv/numbas-lti-provider/start_worker.sh
autostart=true
autorestart=true

[program:numbas_lti_worker_c]
command=/srv/numbas-lti-provider/start_worker.sh
autostart=true
autorestart=true

[group:numbas_lti]
programs=numbas_lti_daphne,numbas_lti_worker_a,numbas_lti_worker_b,numbas_lti_worker_c
priority=999

Start supervisor:

sudo service supervisor start

The server is now running at https://localhost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment