Migrate notes to a dedicated plugin

This commit is contained in:
Régis Behmo 2019-07-03 22:09:33 +08:00 committed by Régis Behmo
parent 07a0323d8e
commit 11e735f4e5
49 changed files with 380 additions and 276 deletions

View File

@ -4,7 +4,7 @@ Note: Breaking changes between versions are indicated by "💥".
## Latest
- [Improvement] Move Xqueue to a dedicated plugin
- [Improvement] Move Xqueue and Student notes to a dedicated plugin
## 3.4.3 (2019-06-24)

View File

@ -14,6 +14,7 @@ package: ## Build a package ready to upload to pypi
package-plugins: ## Build packages for each plugin
cd plugins/minio && python3 setup.py sdist --dist-dir=../../dist/
cd plugins/notes && python3 setup.py sdist --dist-dir=../../dist/
cd plugins/xqueue && python3 setup.py sdist --dist-dir=../../dist/
test: test-lint test-unit test-format test-packages ## Run all tests by decreasing order or priority
@ -85,7 +86,10 @@ ci-bundle: ## Create bundle and run basic tests
./dist/tutor --version
./dist/tutor config printroot
yes "" | ./dist/tutor config save --interactive
./dist/tutor config save --set ACTIVATE_NOTES=true --set ACTIVATE_XQUEUE=true
./dist/tutor config save
./dist/tutor plugins enable notes
./dist/tutor plugins enable xqueue
./releases/github-release: ## Download github-release binary
cd releases/ \
@ -112,8 +116,8 @@ ci-github: ./releases/github-release ## Upload assets to github
--replace
ci-config-images:
tutor plugin enable notes
tutor plugin enable xqueue
tutor config save --set ACTIVATE_NOTES=true
ci-build-images: ci-config-images ## Build docker images
tutor images build all

View File

@ -5,6 +5,10 @@ from tutor.commands.cli import main
# Manually adding plugins to bundle
from tutor.plugins import Plugins
import tutorminio.plugin
import tutornotes.plugin
import tutorxqueue.plugin
Plugins.EXTRA_INSTALLED["minio"] = tutorminio.plugin
Plugins.EXTRA_INSTALLED["notes"] = tutornotes.plugin
Plugins.EXTRA_INSTALLED["xqueue"] = tutorxqueue.plugin
main()

View File

@ -48,7 +48,6 @@ Individual service activation
- ``ACTIVATE_RABBITMQ`` (default: ``true``)
- ``ACTIVATE_SMTP`` (default: ``true``)
- ``ACTIVATE_HTTPS`` (default: ``false``)
- ``ACTIVATE_NOTES`` (default: ``false``)
Every single Open edX service may be (de)activated at will by these configuration parameters. This is useful if you want, for instance, to distribute the various Open edX services on different servers.
@ -63,7 +62,6 @@ Custom images
- ``DOCKER_IMAGE_OPENEDX`` (default: ``"overhangio/openedx:{{ TUTOR_VERSION }}"``)
- ``DOCKER_IMAGE_ANDROID`` (default: ``"overhangio/openedx-android:{{ TUTOR_VERSION }}"``)
- ``DOCKER_IMAGE_FORUM`` (default: ``"overhangio/openedx-forum:{{ TUTOR_VERSION }}"``)
- ``DOCKER_IMAGE_NOTES`` (default: ``"overhangio/openedx-notes:{{ TUTOR_VERSION }}"``)
These configuration parameters define which image to run for each service. By default, the docker image tag matches the Tutor version it was built with.
@ -143,13 +141,8 @@ SMTP
- ``SMTP_USERNAME`` (default: ``""``)
- ``SMTP_PASSWORD`` (default: ``""``)
Optional features
~~~~~~~~~~~~~~~~~
Some optional features may be activated or deactivated during the interactive configuration step (``tutor config save -i``).
SSL/TLS certificates for HTTPS access
*************************************
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``ACTIVATE_HTTPS`` (default: ``false``)
@ -171,21 +164,6 @@ To renew the certificate, run this command once per month::
tutor local https renew
Student notes
*************
- ``ACTIVATE_NOTES`` (default: ``false``)
- ``NOTES_HOST`` (default: ``notes.{{ LMS_HOST }}``)
With `notes <https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/open-release-ironwood.master/exercises_tools/notes.html?highlight=notes>`_, students can annotate portions of the courseware.
.. image:: https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/open-release-ironwood.master/_images/SFD_SN_bodyexample.png
:alt: Notes in action
You should beware that the ``notes.<LMS_HOST>`` domain name should be activated and point to your server. For instance, if your LMS is hosted at http://myopenedx.com, the notes service should be found at http://notes.myopenedx.com.
If you would like to host the notes service at a different domain name, you can set the ``NOTES_HOST`` configuration variable. In particular, in development you should set this configuration variable to ``notes.localhost`` in order to be able to access the notes service from the LMS. Otherwise you will get a "Sorry, we could not search the store for annotations" error.
.. _customise:
Custom Open edX docker image

37
plugins/notes/README.rst Normal file
View File

@ -0,0 +1,37 @@
Students notes plugin for `Tutor <https://docs.tutor.overhang.io>`_
===================================================================
This is a plugin for `Tutor <https://docs.tutor.overhang.io>`_ to easily add the `Open edX note-taking app <https://github.com/edx/edx-notes-api>`_ to an Open edX platform. This app allows students to annotate portions of the courseware (see `the official documentation <https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/open-release-ironwood.master/exercises_tools/notes.html?highlight=notes>`_).
.. image:: https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/open-release-ironwood.master/_images/SFD_SN_bodyexample.png
:alt: Notes in action
Installation
------------
The plugin is currently bundled with the `binary releases of Tutor <https://github.com/overhangio/tutor/releases>`_. If you have installed Tutor from source, you will have to install this plugin from source, too::
pip install tutor-notes
Then, to enable this plugin, run::
tutor plugins enable notes
You should beware that the ``notes.<LMS_HOST>`` domain name should exist and point to your server. For instance, if your LMS is hosted at http://myopenedx.com, the notes service should be found at http://notes.myopenedx.com.
If you would like to host the notes service at a different domain name, you can set the ``NOTES_HOST`` configuration variable (see below). In particular, in development you should set this configuration variable to ``notes.localhost`` in order to be able to access the notes service from the LMS. Otherwise you will get a "Sorry, we could not search the store for annotations" error.
Configuration
-------------
- ``NOTES_MYSQL_PASSWORD`` (default: ``"{{ 8|random_string }}"``)
- ``NOTES_SECRET_KEY`` (default: ``"{{ 24|random_string }}"``)
- ``NOTES_OAUTH2_SECRET`` (default: ``"{{ 24|random_string }}"``)
- ``NOTES_DOCKER_IMAGE`` (default: ``"overhangio/openedx-notes:{{ TUTOR_VERSION }}"``)
- ``NOTES_HOST`` (default: ``"notes.{{ LMS_HOST }}"``)
- ``NOTES_MYSQL_DATABASE`` (default: ``"notes"``)
- ``NOTES_MYSQL_USERNAME`` (default: ``"notes"``)
These values can be modified with ``tutor config save --set PARAM_NAME=VALUE`` commands.

40
plugins/notes/setup.py Normal file
View File

@ -0,0 +1,40 @@
import io
import os
from setuptools import setup
here = os.path.abspath(os.path.dirname(__file__))
with io.open(os.path.join(here, "README.rst"), "rt", encoding="utf8") as f:
readme = f.read()
setup(
name="tutor-notes",
version="0.0.1",
url="https://docs.tutor.overhang.io/",
project_urls={
"Documentation": "https://docs.tutor.overhang.io/",
"Code": "https://github.com/overhangio/tutor/tree/master/plugins/minio",
"Issue tracker": "https://github.com/overhangio/tutor/issues",
"Community": "https://discuss.overhang.io",
},
license="AGPLv3",
author="Overhang.io",
author_email="contact@overhang.io",
description="A Tutor plugin for student notes",
long_description=readme,
packages=["tutornotes"],
include_package_data=True,
python_requires=">=3.5",
entry_points={"tutor.plugin.v0": ["notes = tutornotes.plugin"]},
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Affero General Public License v3",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
],
)

View File

@ -0,0 +1 @@
"ENABLE_EDXNOTES": true

View File

@ -0,0 +1 @@
certbot certonly --standalone -n --agree-tos -m admin@{{ LMS_HOST }} -d {{ NOTES_HOST }}

View File

@ -0,0 +1,32 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: notes
labels:
app.kubernetes.io/name: notes
spec:
selector:
matchLabels:
app.kubernetes.io/name: notes
template:
metadata:
labels:
app.kubernetes.io/name: notes
spec:
containers:
- name: notes
image: {{ DOCKER_REGISTRY }}{{ NOTES_DOCKER_IMAGE }}
ports:
- containerPort: 8000
env:
- name: DJANGO_SETTINGS_MODULE
value: notesserver.settings.tutor
volumeMounts:
- mountPath: /openedx/edx-notes-api/notesserver/settings/tutor.py
name: settings
subPath: tutor.py
volumes:
- name: settings
configMap:
name: notes-settings

View File

@ -0,0 +1,18 @@
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: {{ NOTES_HOST|replace(".", "-") }}
spec:
secretName: {{ NOTES_HOST }}-tls
issuerRef:
name: letsencrypt
commonName: {{ NOTES_HOST }}
dnsNames:
- {{ NOTES_HOST }}
acme:
config:
- http01:
ingress: web
domains:
- {{ NOTES_HOST }}

View File

@ -0,0 +1,6 @@
- host: {{ NOTES_HOST }}
http:
paths:
- backend:
serviceName: nginx
servicePort: {% if ACTIVATE_HTTPS %}443{% else %}80{% endif %}

View File

@ -0,0 +1 @@
- {{ NOTES_HOST }}

View File

@ -0,0 +1,12 @@
---
apiVersion: v1
kind: Service
metadata:
name: notes
spec:
type: NodePort
ports:
- port: 8000
protocol: TCP
selector:
app.kubernetes.io/name: notes

View File

@ -0,0 +1,3 @@
- name: notes-settings
files:
- plugins/notes/apps/notes/settings/tutor.py

View File

@ -0,0 +1,2 @@
"EDXNOTES_PUBLIC_API": "{{ "https" if ACTIVATE_HTTPS else "http" }}://{{ NOTES_HOST }}/api/v1",
"EDXNOTES_INTERNAL_API": "http://notes.openedx:8000/api/v1"

View File

@ -0,0 +1,15 @@
############# Notes: backend store for edX Student Notes
notes:
image: {{ DOCKER_REGISTRY }}{{ NOTES_DOCKER_IMAGE }}
networks:
default:
aliases:
- notes.openedx
environment:
DJANGO_SETTINGS_MODULE: notesserver.settings.tutor
volumes:
- ../apps/notes/settings/tutor.py:/openedx/edx-notes-api/notesserver/settings/tutor.py
- ../../data/notes:/openedx/data
restart: unless-stopped
{% if ACTIVATE_MYSQL %}depends_on:
- mysql{% endif %}

View File

@ -0,0 +1,35 @@
### Student notes service
upstream notes-backend {
server notes:8000 fail_timeout=0;
}
{% if ACTIVATE_HTTPS %}
server {
server_name {{ NOTES_HOST }};
listen 80;
return 301 https://$server_name$request_uri;
}
{% endif %}
server {
{% if ACTIVATE_HTTPS %}listen 443 {{ "" if WEB_PROXY else "ssl" }};{% else %}listen 80;{% endif %}
server_name notes.localhost {{ NOTES_HOST }};
{% if ACTIVATE_HTTPS and not WEB_PROXY %}
ssl_certificate /etc/letsencrypt/live/{{ NOTES_HOST }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ NOTES_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;
proxy_pass http://notes-backend;
}
}

View File

@ -0,0 +1,27 @@
{% if ACTIVATE_HTTPS %}
<VirtualHost *:80>
ServerName {{ NOTES_HOST }}
Redirect / https://notes.{{ LMS_HOST }}/
</VirtualHost>
<VirtualHost *:443>
ServerName {{ NOTES_HOST }}
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/{{ NOTES_HOST }}/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/{{ NOTES_HOST }}/privkey.pem
ProxyPreserveHost On
ProxyRequests On
ProxyPass / http://localhost:{{ NGINX_HTTP_PORT }}/
ProxyPassReverse / http://localhost:{{ NGINX_HTTP_PORT }}/
</VirtualHost>
{% else %}
<VirtualHost *:80>
ServerName {{ NOTES_HOST }}
ProxyPreserveHost On
ProxyRequests On
ProxyPass / http://localhost:{{ NGINX_HTTP_PORT }}/
ProxyPassReverse / http://localhost:{{ NGINX_HTTP_PORT }}/
</VirtualHost>
{% endif %}

View File

@ -0,0 +1,33 @@
server {
listen 80;
server_name {{ NOTES_HOST }};
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;
proxy_pass http://localhost:{{ NGINX_HTTP_PORT }};
}
}
{% if ACTIVATE_HTTPS %}
server {
listen 443 ssl;
server_name {{ NOTES_HOST }};
ssl_certificate /etc/letsencrypt/live/{{ NOTES_HOST }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ NOTES_HOST }}/privkey.pem;
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;
proxy_pass http://localhost:{{ NGINX_HTTPS_PORT }};
}
}
{% endif %}

View File

@ -0,0 +1,35 @@
from glob import glob
import os
HERE = os.path.abspath(os.path.dirname(__file__))
config = {
"add": {
"MYSQL_PASSWORD": "{{ 8|random_string }}",
"SECRET_KEY": "{{ 24|random_string }}",
"OAUTH2_SECRET": "{{ 24|random_string }}",
},
"defaults": {
"DOCKER_IMAGE": "overhangio/openedx-notes:{{ TUTOR_VERSION }}",
"HOST": "notes.{{ LMS_HOST }}",
"MYSQL_DATABASE": "notes",
"MYSQL_USERNAME": "notes",
},
}
templates = os.path.join(HERE, "templates")
hooks = {
"init": ["mysql-client", "lms", "notes"],
"build-image": {"notes": "{{ NOTES_DOCKER_IMAGE }}"},
"remote-image": {"notes": "{{ NOTES_DOCKER_IMAGE }}"},
}
def patches():
all_patches = {}
for path in glob(os.path.join(HERE, "patches", "*")):
with open(path) as patch_file:
name = os.path.basename(path)
content = patch_file.read()
all_patches[name] = content
return all_patches

View File

@ -0,0 +1,5 @@
./manage.py lms --settings=tutor.production manage_user notes notes@{{ LMS_HOST }} --staff --superuser
./manage.py lms --settings=tutor.production 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

View File

@ -0,0 +1,2 @@
mysql -u root --password="{{ MYSQL_ROOT_PASSWORD }}" --host "{{ MYSQL_HOST }}" --port {{ MYSQL_PORT }} -e 'CREATE DATABASE IF NOT EXISTS {{ NOTES_MYSQL_DATABASE }};'
mysql -u root --password="{{ MYSQL_ROOT_PASSWORD }}" --host "{{ MYSQL_HOST }}" --port {{ MYSQL_PORT }} -e 'GRANT ALL ON {{ NOTES_MYSQL_DATABASE }}.* TO "{{ NOTES_MYSQL_USERNAME }}"@"%" IDENTIFIED BY "{{ NOTES_MYSQL_PASSWORD }}";'

View File

@ -1,3 +1,3 @@
- name: xqueue-settings
files:
- apps/xqueue/settings/tutor.py
- plugins/xqueue/apps/xqueue/settings/tutor.py

View File

@ -1,2 +1,3 @@
-e ./plugins/minio
-e ./plugins/notes
-e ./plugins/xqueue

View File

@ -147,6 +147,27 @@ class PluginsTests(unittest.TestCase):
tutor_config.load_plugins(config, defaults)
self.assertEqual("{{ PARAM1 }}", defaults["PLUGIN1_PARAM2"])
def test_configure_add_twice(self):
config = {}
class plugin1:
config = {"add": {"PARAM1": "{{ 10|random_string }}"}}
with unittest.mock.patch.object(
plugins.Plugins, "iter_enabled", return_value=[("plugin1", plugin1)]
):
tutor_config.load_plugins(config, {})
value1 = config["PLUGIN1_PARAM1"]
with unittest.mock.patch.object(
plugins.Plugins, "iter_enabled", return_value=[("plugin1", plugin1)]
):
tutor_config.load_plugins(config, {})
value2 = config["PLUGIN1_PARAM1"]
self.assertEqual(10, len(value1))
self.assertEqual(10, len(value2))
self.assertEqual(value1, value2)
def test_hooks(self):
class plugin1:
hooks = {"init": ["myclient"]}

View File

@ -6,6 +6,8 @@ from .. import images
from .. import opts
from .. import plugins
OPENEDX_IMAGE_NAMES = ["openedx", "forum", "android"]
@click.group(name="images", short_help="Manage docker images")
def images_command():
@ -31,7 +33,7 @@ def build(root, image, no_cache, build_arg):
config = tutor_config.load(root)
# Build base images
for img in openedx_image_names(config):
for img in OPENEDX_IMAGE_NAMES:
if image in [img, "all"]:
tag = get_tag(config, img)
images.build(
@ -79,7 +81,7 @@ def pull(root, image):
def push(root, image):
config = tutor_config.load(root)
# Push base images
for img in openedx_image_names(config):
for img in OPENEDX_IMAGE_NAMES:
if image in [img, "all"]:
tag = get_tag(config, img)
images.push(tag)
@ -98,14 +100,7 @@ def get_tag(config, name):
def image_names(config):
return openedx_image_names(config) + vendor_image_names(config)
def openedx_image_names(config):
openedx_images = ["openedx", "forum", "notes", "android"]
if not config["ACTIVATE_NOTES"]:
openedx_images.remove("notes")
return openedx_images
return OPENEDX_IMAGE_NAMES + vendor_image_names(config)
def vendor_image_names(config):
@ -118,16 +113,9 @@ def vendor_image_names(config):
"rabbitmq",
"smtp",
]
if not config["ACTIVATE_ELASTICSEARCH"]:
vendor_images.remove("elasticsearch")
if not config["ACTIVATE_MEMCACHED"]:
vendor_images.remove("memcached")
if not config["ACTIVATE_MONGODB"]:
vendor_images.remove("mongodb")
if not config["ACTIVATE_MYSQL"]:
vendor_images.remove("mysql")
if not config["ACTIVATE_RABBITMQ"]:
vendor_images.remove("rabbitmq")
for image in vendor_images[:]:
if not config.get("ACTIVATE_" + image.upper(), True):
vendor_images.remove(image)
return vendor_images

View File

@ -1,6 +1,9 @@
import os
import shutil
import click
from .. import config as tutor_config
from .. import env as tutor_env
from .. import fmt
from .. import opts
from .. import plugins
@ -46,6 +49,11 @@ def disable(root, plugin):
config = tutor_config.load_user(root)
plugins.disable(config, plugin)
tutor_config.save(root, config)
plugin_dir = tutor_env.pathjoin(root, "plugins", plugin)
if os.path.exists(plugin_dir):
shutil.rmtree(plugin_dir)
fmt.echo_info(
"You should now re-generate your environment with `tutor config save`."
)

View File

@ -89,15 +89,13 @@ def load_env(config, defaults):
def load_required(config, defaults):
"""
All these keys must be present in the user's config.yml. This includes all values that are generated once and must be kept after that, such as passwords.
All these keys must be present in the user's config.yml. This includes all values
that are generated once and must be kept after that, such as passwords.
"""
for key in [
"SECRET_KEY",
"MYSQL_ROOT_PASSWORD",
"OPENEDX_MYSQL_PASSWORD",
"NOTES_MYSQL_PASSWORD",
"NOTES_SECRET_KEY",
"NOTES_OAUTH2_SECRET",
"ANDROID_OAUTH2_SECRET",
"ID",
]:
@ -115,7 +113,9 @@ def load_plugins(config, defaults):
# Add new config key/values
for key, value in plugin_config.get("add", {}).items():
config[plugin_prefix + key] = env.render_unknown(config, value)
new_key = plugin_prefix + key
if new_key not in config:
config[new_key] = env.render_unknown(config, value)
# Set existing config key/values: here, we do not override existing values
for key, value in plugin_config.get("set", {}).items():
@ -137,6 +137,10 @@ def upgrade_obsolete(config):
config["OPENEDX_MYSQL_DATABASE"] = config.pop("MYSQL_DATABASE")
if "MYSQL_USERNAME" in config:
config["OPENEDX_MYSQL_USERNAME"] = config.pop("MYSQL_USERNAME")
if "ACTIVATE_NOTES" in config:
if config["ACTIVATE_NOTES"]:
plugins.enable(config, "notes")
config.pop("ACTIVATE_NOTES")
if "ACTIVATE_XQUEUE" in config:
if config["ACTIVATE_XQUEUE"]:
plugins.enable(config, "xqueue")

View File

@ -115,7 +115,6 @@ def save_plugin_templates(plugin, plugin_path, root, config):
"""
Save plugin templates to plugins/<plugin name>/*.
Only the "apps" and "build" subfolders are rendered.
TODO we should delete this folder when the plugin is disabled.
"""
for subdir in ["apps", "build"]:
path = os.path.join(plugin_path, plugin, subdir)

View File

@ -12,10 +12,12 @@ def build(path, tag, no_cache=False, build_args=None):
command += ["--build-arg", arg]
utils.docker(*command)
def pull(tag):
fmt.echo_info("Pulling image {}".format(tag))
utils.execute("docker", "pull", tag)
def push(tag):
fmt.echo_info("Pushing image {}".format(tag))
utils.execute("docker", "push", tag)
utils.execute("docker", "push", tag)

View File

@ -128,12 +128,6 @@ def ask_questions(config, defaults):
config,
defaults,
)
ask_bool(
"Activate Student Notes service (https://open.edx.org/features/student-notes)?",
"ACTIVATE_NOTES",
config,
defaults,
)
def ask(question, key, config, defaults):

View File

@ -37,7 +37,7 @@ class BaseRunner:
def initialise(runner):
fmt.echo_info("Initialising all services...")
runner.run("mysql-client", "hooks", "mysql-client", "init")
for service in ["lms", "cms", "forum", "notes"]:
for service in ["lms", "cms", "forum"]:
if runner.is_activated(service):
fmt.echo_info("Initialising {}...".format(service))
runner.run(service, "hooks", service, "init")

View File

@ -1,38 +1 @@
{% if ACTIVATE_NOTES %}
upstream notes-backend {
server notes:8000 fail_timeout=0;
}
{% if ACTIVATE_HTTPS %}
server {
server_name {{ NOTES_HOST }};
listen 80;
return 301 https://$server_name$request_uri;
}
{% endif %}
server {
{% if ACTIVATE_HTTPS %}listen 443 {{ "" if WEB_PROXY else "ssl" }};{% else %}listen 80;{% endif %}
server_name notes.localhost {{ NOTES_HOST }};
{% if ACTIVATE_HTTPS and not WEB_PROXY %}
ssl_certificate /etc/letsencrypt/live/{{ NOTES_HOST }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ NOTES_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;
proxy_pass http://notes-backend;
}
}
{% endif %}
{{ patch("nginx-extra") }}

View File

@ -6,11 +6,11 @@
"OAUTH_OIDC_ISSUER": "http://localhost:8000/oauth2",
"PLATFORM_NAME": "{{ PLATFORM_NAME }}",
"FEATURES": {
{{ patch("common-env-features", separator=",\n", suffix=",")|indent(4) }}
"PREVIEW_LMS_BASE": "preview.{{ LMS_HOST }}",
"DISABLE_STUDIO_SSO_OVER_LMS": {{ "false" if ACTIVATE_HTTPS else "true" }},
"ENABLE_COURSEWARE_INDEX": true,
"ENABLE_LIBRARY_INDEX": true,
"ENABLE_EDXNOTES": {{ "true" if ACTIVATE_NOTES else "false" }}
"ENABLE_LIBRARY_INDEX": true
},
"LMS_ROOT_URL": "{{ "https" if ACTIVATE_HTTPS else "http" }}://{{ LMS_HOST }}",
"CMS_ROOT_URL": "{{ "https" if ACTIVATE_HTTPS else "http" }}://{{ CMS_HOST }}",

View File

@ -6,6 +6,7 @@
"OAUTH_OIDC_ISSUER": "http://localhost:8000/oauth2",
"PLATFORM_NAME": "{{ PLATFORM_NAME }}",
"FEATURES": {
{{ patch("common-env-features", separator=",\n", suffix=",")|indent(4) }}
"PREVIEW_LMS_BASE": "preview.{{ LMS_HOST }}",
"ENABLE_COURSE_DISCOVERY": true,
"ENABLE_COURSEWARE_SEARCH": true,
@ -14,7 +15,6 @@
"ENABLE_GRADE_DOWNLOADS": true,
"ENABLE_MOBILE_REST_API": true,
"ENABLE_OAUTH2_PROVIDER": 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 }}",
@ -38,10 +38,6 @@
"EMAIL_HOST": "{{ SMTP_HOST }}",
"EMAIL_PORT": {{ SMTP_PORT }},
"HTTPS": "{{ "on" if ACTIVATE_HTTPS else "off" }}",
{% if ACTIVATE_NOTES %}
"EDXNOTES_PUBLIC_API": "{{ "https" if ACTIVATE_HTTPS else "http" }}://{{ NOTES_HOST }}/api/v1",
"EDXNOTES_INTERNAL_API": "http://notes.openedx:8000/api/v1",
{% endif %}
"LANGUAGE_CODE": "{{ LANGUAGE_CODE }}",
"LOGIN_REDIRECT_WHITELIST": ["{{ CMS_HOST }}", "studio.localhost"],
{% if ACTIVATE_HTTPS %}"SESSION_COOKIE_DOMAIN": ".{{ LMS_HOST|common_domain(CMS_HOST) }}",{% endif %}

View File

@ -3,9 +3,6 @@
SECRET_KEY: "{{ 24|random_string }}"
MYSQL_ROOT_PASSWORD: "{{ 8|random_string }}"
OPENEDX_MYSQL_PASSWORD: "{{ 8|random_string }}"
NOTES_MYSQL_PASSWORD: "{{ 8|random_string }}"
NOTES_SECRET_KEY: "{{ 24|random_string }}"
NOTES_OAUTH2_SECRET: "{{ 24|random_string }}"
ANDROID_OAUTH2_SECRET: "{{ 24|random_string }}"
ID: "{{ 24|random_string }}"
@ -21,7 +18,6 @@ ACTIVATE_HTTPS: false
ACTIVATE_MEMCACHED: true
ACTIVATE_MONGODB: true
ACTIVATE_MYSQL: true
ACTIVATE_NOTES: false
ACTIVATE_RABBITMQ: true
ACTIVATE_SMTP: true
CMS_HOST: "studio.{{ LMS_HOST }}"
@ -34,7 +30,6 @@ ANDROID_RELEASE_KEY_ALIAS: "android release key alias"
DOCKER_IMAGE_OPENEDX: "overhangio/openedx:{{ TUTOR_VERSION }}"
DOCKER_IMAGE_ANDROID: "overhangio/openedx-android:{{ TUTOR_VERSION }}"
DOCKER_IMAGE_FORUM: "overhangio/openedx-forum:{{ TUTOR_VERSION }}"
DOCKER_IMAGE_NOTES: "overhangio/openedx-notes:{{ TUTOR_VERSION }}"
DOCKER_IMAGE_MEMCACHED: "memcached:1.4.38"
DOCKER_IMAGE_MONGODB: "mongo:3.2.16"
DOCKER_IMAGE_MYSQL: "mysql:5.6.36"
@ -62,9 +57,6 @@ MYSQL_HOST: "mysql"
MYSQL_PORT: 3306
NGINX_HTTP_PORT: 80
NGINX_HTTPS_PORT: 443
NOTES_HOST: "notes.{{ LMS_HOST }}"
NOTES_MYSQL_DATABASE: "notes"
NOTES_MYSQL_USERNAME: "notes"
PLATFORM_NAME: "My Open edX"
PLUGINS: []
RABBITMQ_HOST: "rabbitmq"

View File

@ -1,3 +1,2 @@
certbot certonly --standalone -n --agree-tos -m admin@{{ LMS_HOST }} -d {{ LMS_HOST }} -d {{ CMS_HOST }} -d preview.{{ LMS_HOST }}
{% if ACTIVATE_NOTES %}certbot certonly --standalone -n --agree-tos -m admin@{{ LMS_HOST }} -d {{ NOTES_HOST }}{% endif %}
{{ patch("https-create") }}

View File

@ -4,12 +4,4 @@ dockerize -wait tcp://{{ MYSQL_HOST }}:{{ MYSQL_PORT }} -timeout 20s
./manage.py lms --settings=tutor.production create_oauth2_client \
"http://androidapp.com" "http://androidapp.com/redirect" public \
--client_id android --client_secret {{ ANDROID_OAUTH2_SECRET }} \
--trusted
{% if ACTIVATE_NOTES %}
./manage.py lms --settings=tutor.production manage_user notes notes@{{ LMS_HOST }} --staff --superuser
./manage.py lms --settings=tutor.production 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 %}
--trusted

View File

@ -15,9 +15,4 @@ done
echo "MySQL is up and running"
mysql -u root --password="{{ MYSQL_ROOT_PASSWORD }}" --host "{{ MYSQL_HOST }}" --port {{ MYSQL_PORT }} -e 'CREATE DATABASE IF NOT EXISTS {{ OPENEDX_MYSQL_DATABASE }};'
mysql -u root --password="{{ MYSQL_ROOT_PASSWORD }}" --host "{{ MYSQL_HOST }}" --port {{ MYSQL_PORT }} -e 'GRANT ALL ON {{ OPENEDX_MYSQL_DATABASE }}.* TO "{{ OPENEDX_MYSQL_USERNAME }}"@"%" IDENTIFIED BY "{{ OPENEDX_MYSQL_PASSWORD }}";'
{% if ACTIVATE_NOTES %}
mysql -u root --password="{{ MYSQL_ROOT_PASSWORD }}" --host "{{ MYSQL_HOST }}" --port {{ MYSQL_PORT }} -e 'CREATE DATABASE IF NOT EXISTS {{ NOTES_MYSQL_DATABASE }};'
mysql -u root --password="{{ MYSQL_ROOT_PASSWORD }}" --host "{{ MYSQL_HOST }}" --port {{ MYSQL_PORT }} -e 'GRANT ALL ON {{ NOTES_MYSQL_DATABASE }}.* TO "{{ NOTES_MYSQL_USERNAME }}"@"%" IDENTIFIED BY "{{ NOTES_MYSQL_PASSWORD }}";'
{% endif %}
mysql -u root --password="{{ MYSQL_ROOT_PASSWORD }}" --host "{{ MYSQL_HOST }}" --port {{ MYSQL_PORT }} -e 'GRANT ALL ON {{ OPENEDX_MYSQL_DATABASE }}.* TO "{{ OPENEDX_MYSQL_USERNAME }}"@"%" IDENTIFIED BY "{{ OPENEDX_MYSQL_PASSWORD }}";'

View File

@ -355,40 +355,6 @@ spec:
image: {{ DOCKER_REGISTRY }}{{ DOCKER_IMAGE_MYSQL }}
command: ["sh", "-e", "-c"]
args: ["while true; do echo 'ready'; sleep 10; done"]
{% if ACTIVATE_NOTES %}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: notes
labels:
app.kubernetes.io/name: notes
spec:
selector:
matchLabels:
app.kubernetes.io/name: notes
template:
metadata:
labels:
app.kubernetes.io/name: notes
spec:
containers:
- name: notes
image: {{ DOCKER_REGISTRY }}{{ DOCKER_IMAGE_NOTES }}
ports:
- containerPort: 8000
env:
- name: DJANGO_SETTINGS_MODULE
value: notesserver.settings.tutor
volumeMounts:
- mountPath: /openedx/edx-notes-api/notesserver/settings/tutor.py
name: settings
subPath: tutor.py
volumes:
- name: settings
configMap:
name: notes-settings
{% endif %}
{% if ACTIVATE_SMTP %}
---
apiVersion: apps/v1

View File

@ -1,4 +1,4 @@
---{% set hosts = [LMS_HOST, "preview." + LMS_HOST, CMS_HOST] %}{% if ACTIVATE_NOTES %}{% set hosts = hosts + [NOTES_HOST] %}{% endif %}
---{% set hosts = [LMS_HOST, "preview." + LMS_HOST, CMS_HOST] %}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
@ -23,9 +23,8 @@ spec:
tls:
- hosts:
{% for host in hosts %}
- {{ host }}
- {{ host }}{% endfor %}
{{ patch("k8s-ingress-tls-hosts")|indent(6) }}
{% endfor %}
secretName: letsencrypt
{%endif%}
{% if ACTIVATE_HTTPS %}
@ -63,25 +62,5 @@ spec:
domains:
- {{ LMS_HOST }}
- {{ CMS_HOST }}
{% if ACTIVATE_NOTES %}
---
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: {{ NOTES_HOST|replace(".", "-") }}
spec:
secretName: {{ NOTES_HOST }}-tls
issuerRef:
name: letsencrypt
commonName: {{ NOTES_HOST }}
dnsNames:
- {{ NOTES_HOST }}
acme:
config:
- http01:
ingress: web
domains:
- {{ NOTES_HOST }}
{% endif %}
{{ patch("k8s-ingress-certificates") }}
{% endif %}

View File

@ -106,20 +106,6 @@ spec:
name: https
selector:
app.kubernetes.io/name: nginx
{% if ACTIVATE_FORUM %}
---
apiVersion: v1
kind: Service
metadata:
name: notes
spec:
type: NodePort
ports:
- port: 8000
protocol: TCP
selector:
app.kubernetes.io/name: notes
{% endif %}
{% if ACTIVATE_RABBITMQ %}
---
apiVersion: v1

View File

@ -34,7 +34,4 @@ configMapGenerator:
- {{ file }}{% endfor %}
{% if ACTIVATE_MYSQL %}- name: mysql-config
env: apps/mysql/auth.env{% endif %}
{% if ACTIVATE_NOTES %}- name: notes-settings
files:
- apps/notes/settings/tutor.py{% endif %}
{{ patch("kustomization-configmapgenerator") }}

View File

@ -75,7 +75,6 @@ services:
depends_on:
{% if ACTIVATE_LMS %}- lms{% endif %}
{% if ACTIVATE_CMS %}- cms {% endif %}
{% if ACTIVATE_NOTES %}- notes{% endif %}
{% if ACTIVATE_RABBITMQ %}
rabbitmq:
@ -190,22 +189,4 @@ services:
- cms
{% endif %}
{% if ACTIVATE_NOTES %}
############# Notes: backend store for edX Student Notes
notes:
image: {{ DOCKER_REGISTRY }}{{ DOCKER_IMAGE_NOTES }}
networks:
default:
aliases:
- notes.openedx
environment:
DJANGO_SETTINGS_MODULE: notesserver.settings.tutor
volumes:
- ../apps/notes/settings/tutor.py:/openedx/edx-notes-api/notesserver/settings/tutor.py
- ../../data/notes:/openedx/data
restart: unless-stopped
{% if ACTIVATE_MYSQL %}depends_on:
- mysql{% endif %}
{% endif %}
{{ patch("local-docker-compose-services")|indent(2) }}

View File

@ -24,28 +24,10 @@
ProxyPass / http://localhost:{{ NGINX_HTTP_PORT }}/
ProxyPassReverse / http://localhost:{{ NGINX_HTTP_PORT }}/
</VirtualHost>
{% if ACTIVATE_NOTES %}
<VirtualHost *:80>
ServerName {{ NOTES_HOST }}
Redirect / https://notes.{{ CMS_HOST }}/
</VirtualHost>
<VirtualHost *:443>
ServerName {{ NOTES_HOST }}
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/{{ NOTES_HOST }}/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/{{ NOTES_HOST }}/privkey.pem
ProxyPreserveHost On
ProxyRequests On
ProxyPass / http://localhost:{{ NGINX_HTTP_PORT }}/
ProxyPassReverse / http://localhost:{{ NGINX_HTTP_PORT }}/
</VirtualHost>
{% endif %}
{% else %}
<VirtualHost *:80>
ServerName {{ LMS_HOST }}
ServerAlias preview.{{ LMS_HOST }} {{ CMS_HOST }} {% if ACTIVATE_NOTES %}{{ NOTES_HOST }}{% endif %}
ServerAlias preview.{{ LMS_HOST }} {{ CMS_HOST }}
ProxyPreserveHost On
ProxyRequests On
@ -53,3 +35,5 @@
ProxyPassReverse / http://localhost:{{ NGINX_HTTP_PORT }}/
</VirtualHost>
{% endif %}
{{ patch("proxy-apache") }}

View File

@ -33,38 +33,4 @@ server {
}
{% endif %}
{% if ACTIVATE_NOTES %}
server {
listen 80;
server_name {{ NOTES_HOST }};
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;
proxy_pass http://localhost:{{ NGINX_HTTP_PORT }};
}
}
{% if ACTIVATE_HTTPS %}
server {
listen 443 ssl;
server_name {{ NOTES_HOST }};
ssl_certificate /etc/letsencrypt/live/{{ NOTES_HOST }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ NOTES_HOST }}/privkey.pem;
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;
proxy_pass http://localhost:{{ NGINX_HTTPS_PORT }};
}
}
{% endif %}
{% endif %}
{{ patch("proxy-nginx") }}