diff --git a/CHANGELOG.md b/CHANGELOG.md index bea855d..e7c1a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +- 2018-09-15 [Feature] Add student notes as an optional feature - 2018-09-15 [Feature] Add templates to configurator container, which can now be run separately - 2018-09-15 [Improvement] Rename "up" and "daemon" commands to "run" and "daemonize" - 2018-09-15 [Feature] Activate course search and discovery diff --git a/Makefile b/Makefile index 1eb4a18..90d85a4 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,10 @@ ifeq ($(ACTIVATE_XQUEUE), 1) extra_migrate_targets += migrate-xqueue DOCKER_COMPOSE += -f docker-compose-xqueue.yml endif +ifeq ($(ACTIVATE_NOTES), 1) + extra_migrate_targets += migrate-notes + DOCKER_COMPOSE += -f docker-compose-notes.yml +endif DOCKER_COMPOSE_RUN = $(DOCKER_COMPOSE) run --rm DOCKER_COMPOSE_RUN_OPENEDX = $(DOCKER_COMPOSE_RUN) -e USERID=$(USERID) -e SETTINGS=$(EDX_PLATFORM_SETTINGS) @@ -31,16 +35,18 @@ all: configure $(post_configure_targets) update migrate assets daemonize configure: build-configurator docker run --rm -it --volume="$(PWD)/config:/openedx/config" \ - -e USERID=$(USERID) -e SILENT=$(SILENT) -e SETTING_ACTIVATE_HTTPS=$(ACTIVATE_HTTPS) -e SETTING_ACTIVATE_XQUEUE=$(ACTIVATE_XQUEUE) \ + -e USERID=$(USERID) -e SILENT=$(SILENT) \ + -e SETTING_ACTIVATE_HTTPS=$(ACTIVATE_HTTPS) -e SETTING_ACTIVATE_NOTES=$(ACTIVATE_NOTES) -e SETTING_ACTIVATE_XQUEUE=$(ACTIVATE_XQUEUE) \ regis/openedx-configurator:hawthorn update: $(DOCKER_COMPOSE) pull +migrate: provision migrate-openedx migrate-forum $(extra_migrate_targets) oauth2 provision: $(DOCKER_COMPOSE_RUN) lms bash -c "dockerize -wait tcp://mysql:3306 -timeout 20s && bash /openedx/config/provision.sh" - -migrate: provision migrate-openedx migrate-forum $(extra_migrate_targets) +oauth2: + $(DOCKER_COMPOSE_RUN) lms /openedx/config/oauth2.sh migrate-openedx: $(DOCKER_COMPOSE_RUN) lms bash -c "dockerize -wait tcp://mysql:3306 -timeout 20s && ./manage.py lms migrate" @@ -51,6 +57,9 @@ migrate-forum: $(DOCKER_COMPOSE_RUN) forum bash -c "bundle exec rake search:initialize && \ bundle exec rake search:rebuild_index" +migrate-notes: + $(DOCKER_COMPOSE_RUN) notes ./manage.py migrate + migrate-xqueue: $(DOCKER_COMPOSE_RUN) xqueue ./manage.py migrate @@ -140,7 +149,7 @@ android-push: android-dockerhub: android-build android-push #################### Build images -build: build-openedx build-configurator build-forum build-xqueue +build: build-openedx build-configurator build-forum build-notes build-xqueue build-openedx: docker build -t regis/openedx:latest -t regis/openedx:hawthorn openedx/ @@ -148,6 +157,8 @@ build-configurator: docker build -t regis/openedx-configurator:latest -t regis/openedx-configurator:hawthorn configurator/ build-forum: docker build -t regis/openedx-forum:latest -t regis/openedx-forum:hawthorn forum/ +build-notes: + docker build -t regis/openedx-notes:latest -t regis/openedx-notes:hawthorn notes/ build-xqueue: docker build -t regis/openedx-xqueue:latest -t regis/openedx-xqueue:hawthorn xqueue/ @@ -162,6 +173,9 @@ push-configurator: push-forum: docker push regis/openedx-forum:hawthorn docker push regis/openedx-forum:latest +push-notes: + docker push regis/openedx-notes:hawthorn + docker push regis/openedx-notes:latest push-xqueue: docker push regis/openedx-xqueue:hawthorn docker push regis/openedx-xqueue:latest diff --git a/README.md b/README.md index 8041fcc..0eb4f39 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,14 @@ To renew the certificate, run this command once per month: make https-certificate-renew +### Student notes (`ACTIVATE_NOTES`) + +With [notes](https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/open-release-hawthorn.master/exercises_tools/notes.html?highlight=notes), students can annotate portions of the courseware. + +![Notes in action](https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/open-release-hawthorn.master/_images/SFD_SN_bodyexample.png) + +You should beware that the `notes.` domain name should be activated and point to your server. For instance, if your LMS is hosted at [myopenedx.com](), the notes service should be found at [notes.myopenedx.com](). Student browsers will access this domain name to fetch their notes. + ### Xqueue (`ACTIVATE_XQUEUE`) [Xqueue](https://github.com/edx/xqueue) is for grading problems with external services. If you don't know what it is, you probably don't need it. diff --git a/configurator/bin/configure.py b/configurator/bin/configure.py index 3d35ed5..8180a25 100755 --- a/configurator/bin/configure.py +++ b/configurator/bin/configure.py @@ -132,6 +132,16 @@ def interactive(args): 'MYSQL_PASSWORD', "", random_string(8) ).add( 'MONGODB_DATABASE', "", 'openedx' + ).add( + 'NOTES_MYSQL_DATABASE', "", 'notes', + ).add( + 'NOTES_MYSQL_USERNAME', "", 'notes', + ).add( + 'NOTES_MYSQL_PASSWORD', "", random_string(8) + ).add( + 'NOTES_SECRET_KEY', "", random_string(24) + ).add( + 'NOTES_OAUTH2_SECRET', "", random_string(24) ).add( 'XQUEUE_AUTH_USERNAME', "", 'lms' ).add( @@ -144,6 +154,8 @@ def interactive(args): 'XQUEUE_MYSQL_PASSWORD', "", random_string(8) ).add( 'XQUEUE_SECRET_KEY', "", random_string(24) + ).add_bool( + 'ACTIVATE_NOTES', "", False ).add_bool( 'ACTIVATE_HTTPS', "", False ).add_bool( diff --git a/configurator/templates/letsencrypt/certonly.sh b/configurator/templates/letsencrypt/certonly.sh index 714a7ba..98f88a5 100755 --- a/configurator/templates/letsencrypt/certonly.sh +++ b/configurator/templates/letsencrypt/certonly.sh @@ -1,2 +1,2 @@ #!/bin/sh -certbot certonly --standalone -n --agree-tos -m admin@{{ LMS_HOST }} -d {{ LMS_HOST }} -d {{ CMS_HOST }} -d preview.{{ LMS_HOST }} +certbot certonly --standalone -n --agree-tos -m admin@{{ LMS_HOST }} -d {{ LMS_HOST }} -d {{ CMS_HOST }} -d preview.{{ LMS_HOST }} {% if ACTIVATE_NOTES %} -d notes.{{ LMS_HOST }}{% endif %} diff --git a/configurator/templates/nginx/notes.conf b/configurator/templates/nginx/notes.conf new file mode 100644 index 0000000..552eff2 --- /dev/null +++ b/configurator/templates/nginx/notes.conf @@ -0,0 +1,33 @@ +{% if ACTIVATE_HTTPS %} +server { + server_name notes.{{ LMS_HOST }}; + listen 80; + return 301 https://$server_name$request_uri; +} +{% endif %} + +server { + listen {{ "443 ssl" if ACTIVATE_HTTPS else "80" }}; + server_name notes.localhost notes.{{ LMS_HOST }}; + + {% if ACTIVATE_HTTPS %} + ssl_certificate /etc/letsencrypt/live/notes.{{ LMS_HOST }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/notes.{{ LMS_HOST }}/privkey.pem; + {% endif %} + + # Disables server version feedback on pages and in headers + server_tokens off; + + location / { + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Host $http_host; + proxy_redirect off; + + # Docker resolver + resolver 127.0.0.11 valid=10s; + set $upstream notes; + proxy_pass http://$upstream:8000; + } +} diff --git a/configurator/templates/notes/universal.py b/configurator/templates/notes/universal.py new file mode 100644 index 0000000..73c2032 --- /dev/null +++ b/configurator/templates/notes/universal.py @@ -0,0 +1,27 @@ +from .common import * + +SECRET_KEY = '{{ NOTES_SECRET_KEY }}' +ALLOWED_HOSTS = ['localhost', 'notes', 'notes.openedx', 'notes.localhost', 'notes.{{ LMS_HOST }}'] + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': '{{ NOTES_MYSQL_DATABASE }}', + 'USER': '{{ NOTES_MYSQL_USERNAME }}', + 'PASSWORD': '{{ NOTES_MYSQL_PASSWORD }}', + 'HOST': 'mysql', + } +} + +CLIENT_ID = 'notes' +CLIENT_SECRET = '{{ NOTES_OAUTH2_SECRET }}' + +HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': 'notesserver.highlight.ElasticsearchSearchEngine', + 'URL': 'http://elasticsearch:9200/', + 'INDEX_NAME': 'notes', + }, +} + +LOGGING['handlers']['local'] = LOGGING['handlers']['console'].copy() diff --git a/configurator/templates/openedx/lms.env.json b/configurator/templates/openedx/lms.env.json index a9d52b6..03ce46d 100644 --- a/configurator/templates/openedx/lms.env.json +++ b/configurator/templates/openedx/lms.env.json @@ -7,9 +7,11 @@ "PLATFORM_NAME": "{{ PLATFORM_NAME }}", "FEATURES": { "PREVIEW_LMS_BASE": "preview.{{ LMS_HOST }}", + "ENABLE_OAUTH2_PROVIDER": true, "ENABLE_COURSE_DISCOVERY": true, "ENABLE_COURSEWARE_SEARCH": true, - "ENABLE_DASHBOARD_SEARCH": true + "ENABLE_DASHBOARD_SEARCH": true, + "ENABLE_EDXNOTES": {{ "true" if ACTIVATE_NOTES else "false" }} }, "LMS_ROOT_URL": "{{ "https" if ACTIVATE_HTTPS else "http" }}://{{ LMS_HOST }}", "CMS_ROOT_URL": "{{ "https" if ACTIVATE_HTTPS else "http" }}://{{ CMS_HOST }}", @@ -28,6 +30,10 @@ }], "EMAIL_BACKEND": "django.core.mail.backends.smtp.EmailBackend", "EMAIL_HOST": "smtp", + {% if ACTIVATE_NOTES %} + "EDXNOTES_PUBLIC_API": "{{ "https" if ACTIVATE_HTTPS else "http" }}://notes.{{ LMS_HOST }}/api/v1", + "EDXNOTES_INTERNAL_API": "http://notes.openedx:8000/api/v1", + {% endif %} "CACHES": { "default": { "KEY_PREFIX": "default", diff --git a/configurator/templates/openedx/oauth2.sh b/configurator/templates/openedx/oauth2.sh new file mode 100755 index 0000000..5e6aac3 --- /dev/null +++ b/configurator/templates/openedx/oauth2.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +{% if ACTIVATE_NOTES %} +./manage.py lms manage_user notes notes@{{ LMS_HOST }} --staff --superuser +./manage.py lms create_oauth2_client \ + "http://notes.openedx:8000" \ + "http://notes.openedx:8000/complete/edx-oidc/" \ + confidential \ + --client_name edx-notes --client_id notes --client_secret {{ NOTES_OAUTH2_SECRET }} \ + --trusted --logout_uri "http://notes.openedx:8000/logout/" --username notes +{% endif %} diff --git a/configurator/templates/openedx/provision.sh b/configurator/templates/openedx/provision.sh index 75f3d18..41e5951 100755 --- a/configurator/templates/openedx/provision.sh +++ b/configurator/templates/openedx/provision.sh @@ -1,6 +1,11 @@ mysql -u root --password="{{ MYSQL_PASSWORD }}" --host "mysql" -e 'CREATE DATABASE IF NOT EXISTS {{ MYSQL_DATABASE }};' mysql -u root --password="{{ MYSQL_PASSWORD }}" --host "mysql" -e 'GRANT ALL ON {{ MYSQL_DATABASE }}.* TO "{{ MYSQL_USERNAME }}"@"%" IDENTIFIED BY "{{ MYSQL_PASSWORD }}";' +{% if ACTIVATE_NOTES %} +mysql -u root --password="{{ MYSQL_PASSWORD }}" --host "mysql" -e 'CREATE DATABASE IF NOT EXISTS {{ NOTES_MYSQL_DATABASE }};' +mysql -u root --password="{{ MYSQL_PASSWORD }}" --host "mysql" -e 'GRANT ALL ON {{ NOTES_MYSQL_DATABASE }}.* TO "{{ NOTES_MYSQL_USERNAME }}"@"%" IDENTIFIED BY "{{ NOTES_MYSQL_PASSWORD }}";' +{% endif %} + {% if ACTIVATE_XQUEUE %} mysql -u root --password="{{ MYSQL_PASSWORD }}" --host "mysql" -e 'CREATE DATABASE IF NOT EXISTS {{ XQUEUE_MYSQL_DATABASE }};' mysql -u root --password="{{ MYSQL_PASSWORD }}" --host "mysql" -e 'GRANT ALL ON {{ XQUEUE_MYSQL_DATABASE }}.* TO "{{ XQUEUE_MYSQL_USERNAME }}"@"%" IDENTIFIED BY "{{ XQUEUE_MYSQL_PASSWORD }}";' diff --git a/configurator/templates/openedx/universal/lms/production.py b/configurator/templates/openedx/universal/lms/production.py index b8698b7..848c409 100644 --- a/configurator/templates/openedx/universal/lms/production.py +++ b/configurator/templates/openedx/universal/lms/production.py @@ -35,6 +35,9 @@ ALLOWED_HOSTS = [ # Required to display all courses on start page SEARCH_SKIP_ENROLLMENT_START_DATE_FILTERING = True +# Allow insecure oauth2 for local interaction with local containers +OAUTH_ENFORCE_SECURE = False + DEFAULT_FROM_EMAIL = ENV_TOKENS['CONTACT_EMAIL'] DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS['CONTACT_EMAIL'] SERVER_EMAIL = ENV_TOKENS['CONTACT_EMAIL'] diff --git a/docker-compose-notes.yml b/docker-compose-notes.yml new file mode 100644 index 0000000..da4ca92 --- /dev/null +++ b/docker-compose-notes.yml @@ -0,0 +1,18 @@ +version: "3" +services: + + ############# Notes: backend store for edX Student Notes + notes: + image: regis/openedx-notes:hawthorn + build: + context: ./notes + networks: + default: + aliases: + - notes.openedx + volumes: + - ./config/notes:/openedx/config + - ./data/notes:/openedx/data + restart: unless-stopped + depends_on: + - mysql diff --git a/notes/Dockerfile b/notes/Dockerfile new file mode 100644 index 0000000..63eb5bd --- /dev/null +++ b/notes/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:16.04 + +RUN apt update && \ + apt upgrade -y && \ + apt install -y language-pack-en git python-pip libmysqlclient-dev + +RUN mkdir /openedx +RUN git clone https://github.com/edx/edx-notes-api --branch open-release/hawthorn.1 --depth 1 /openedx/edx-notes-api +WORKDIR /openedx/edx-notes-api + +RUN pip install -r requirements/base.txt + +ENV DJANGO_SETTINGS_MODULE notesserver.settings.universal +RUN ln -s /openedx/config/universal.py notesserver/settings/universal.py + +EXPOSE 8000 +CMD gunicorn --name notes --bind=0.0.0.0:8000 --max-requests=1000 notesserver.wsgi:application