These instructions are checked against Ubuntu, but they should work for Debian and probably for many derivatives of it.
$ lsb_release -a
Distributor ID: Ubuntu
Description: Ubuntu 18.04.1 LTS
Release: 18.04
Codename: bionic
A+ can be deployed with Apache 2 or NGINX. Bot web servers work well enough and can support Shibboleth authentication too. Sadly, combination of NGINX, uWSGI protocol, uWSGI and Shibboleth is not yet tested, but the HTTP proxy version works.
Aalto University is currently running following combinations on Ubuntu 20.04 or Ubuntu 18.04:
- Apache, uWSGI protocol, uWSGI, Shibboleth, uWSGI logs written to a file
- NGINX, HTTP proxy, gunicorn, Shibboleth, Gunicorn logs written to journald
- NGINX, uWSGI protocol, uWSGI, uWSGI logs written to journald
-
Install required packages
sudo apt-get install \ git gettext \ postgresql libpq-dev memcached \ python3-virtualenv \ python3-certifi python3-lz4 python3-reportlab python3-reportlab-accel \ build-essential libxml2-dev libxslt-dev python3-dev python3-venv \ libpcre3 libpcre3-dev
-
Create a new user for a-plus
sudo adduser --system --group \ --shell /bin/bash --home /srv/aplus \ --gecos "A-plus LMS webapp server" \ aplus
-
Create a database and add permissions
sudo -Hu postgres createuser aplus sudo -Hu postgres createdb -O aplus aplus
-
Create a run directory
echo "d /run/aplus 0750 aplus www-data - -" \ | sudo tee /etc/tmpfiles.d/aplus.conf > /dev/null sudo systemd-tmpfiles --create
-
Create RSA keys for JWT authentication
# generate private key openssl genrsa -out private.pem 2048 # extract public key openssl rsa -in private.pem -out public.pem -pubout
-
Change to our service user
sudo -u aplus -Hi
-
Clone the Django application
# as user aplus in /srv/aplus git clone --branch production https://github.com/apluslms/a-plus.git mkdir a-plus/static \ a-plus/media
-
Install virtualenv
# as user aplus in /srv/aplus python3 -m venv venv source ~/venv/bin/activate pip install -r a-plus/requirements_prod.txt pip install -r a-plus/requirements.txt
-
Configure Django
# as user aplus in /srv/aplus cat > a-plus/aplus/local_settings.py <<EOF BASE_URL = "https://$(hostname)/" SERVER_EMAIL = "aplus@$(hostname)" EOF awk '/(BASE_URL|DEBUG|SECRET_KEY|SERVER_EMAIL)/ {next}; /^## (Database|Cache|Logging)/ {while (/^#/ && getline>0); next} 1' \ a-plus/aplus/local_settings.example.py >> a-plus/aplus/local_settings.py cat >> a-plus/aplus/local_settings.py <<EOF ## Database DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'aplus', } } ## Cache CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': '127.0.0.1:11211', } } ## Session SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_COOKIE_SECURE = True EOF
Define the branding settings in
aplus/local_settings.py
:BRAND_NAME
,BRAND_INSTITUTION_NAME
,BRAND_INSTITUTION_NAME_FI
, etc. Checksettings.py
andlocal_settings.example.py
.Ensure that you use
DEBUG = False
in production (local_setting.py
).Fill the
APLUS_AUTH
settings. Checksettings.py
andlocal_settings.example.py
. -
Run Django deployment tasks
# as user aplus in /srv/aplus . venv/bin/activate pushd a-plus # migrate db ./manage.py migrate # compile localisation files ./manage.py compilemessages # collect static files to be served via dedicated web server ./manage.py collectstatic --no-input popd
-
Create uWSGI configuration
# as user aplus in /srv/aplus cp ~/a-plus/doc/uwsgi-aplus-*.ini ~
NOTE: Select number of processes and threads based on number of of CPUs. Probably a good number is around two times cpus for the web (as there is a lot of io wait) and number of cpus for the API.
You can gracefully chain reload uWSGI services by touching (editing) above files. In addition,
sudo systemctl restart aplus-*-uwsgi.service
will also do a chain reload. To fully reload python engine, dostop
and thenstart
for these services. -
End
Exit the aplus user shell and return to the admin
exit
To configure Shibboleth, follow a guide for your federation. Because A+ is used a lot in Finnish Universities, we have some examples using HAKA, the identity federation for Finnish universities.
For HAKA, you need at least schemas from here and metadata certificate from here. In addition, you can start from apache2/shibboleth2.xml or nginx/shibboleth2.xml. More details below for both web servers.
Here is a command to create sp-key.pem
and sp-cert.pem
files.
As of writing, shib-keygen
does create too small keys and uses sha1 for hashing.
Due to security considerations, you should use following instead:
# set your domain here and then copy-paste command below
host=$(hostname)
entityid=https://$host
cd /etc/shibboleth
printf '[req]\ndistinguished_name=req\n[san]\nsubjectAltName=DNS:%s, URI:%s\n' "$host" "$entityid" | \
openssl req -x509 -sha256 -nodes \
-newkey rsa:4096 -keyout sp-key.pem \
-days 3650 -out sp-cert.pem \
-subj "/CN=$host" -extensions san -config /dev/stdin
chown _shibd:_shibd sp-cert.pem sp-key.pem
chmod 0400 sp-key.pem
You can print the certificate information with this command:
openssl x509 -in /etc/shibboleth/sp-cert.pem -noout -text
Resources for Apache 2 configuration can be found under apache2 directory.
Following instructions expect that the applocation is installed under /srv/aplus/a-plus/
.
-
Install packages
sudo apt-get install apache2 libapache2-mod-uwsgi sudo a2enmod uwsgi sudo a2enmod ssl
-
Configure Apache 2
sudo ln -s ../sites-available/$(hostname).conf /etc/apache2/sites-enabled/000-$(hostname).conf sed -e "s/__HOSTNAME__/$(hostname)/g" /srv/aplus/a-plus/doc/apache2/aplus-apache2.conf \ | sudo tee /etc/apache2/sites-available/$(hostname).conf > /dev/null sudo a2enmod rewrite sudo a2dissite 000-default
-
Create systemd service files for uWSGI processes
sudo cp /srv/aplus/a-plus/doc/apache2/aplus-*-uwsgi.service \ /etc/systemd/system sudo systemctl daemon-reload sudo systemctl enable aplus-web-uwsgi.service aplus-api-uwsgi.service sudo systemctl start aplus-web-uwsgi.service aplus-api-uwsgi.service
-
Reload Apache 2
sudo systemctl restart apache2.service
If you are using shibboleth
-
Install Shibboleth 2
sudo apt-get install libapache2-mod-shib2 sudo a2enmod shib2
-
Configure Shibboleth for your federation or organization
Configuration is under directory
/etc/shibboleth
. For HAKA, there is apache2/shibboleth2.xml. -
Shibboleth configuration in The
local_settings.py
Map your federations variables to ones used in A+. Most of the values are common, so only defining
PREFIX
should be enough. Currently,STUDENT_DOMAIN
is a required variable, as A+ presumes student numbers to be from a single domain. Rest of the options are documented insettings.py
.sudo tee -a /srv/aplus/a-plus/aplus/local_settings.py << EOF # Shibboleth SHIBBOLETH_ENVIRONMENT_VARS = { 'PREFIX': 'SHIB_', 'STUDENT_DOMAIN': 'example.com', # XXX: change this! } EOF
-
Reload shibboleth
sudo systemctl restart shibd.service
Resources for NGINX configuration can be found under nginx directory.
Following instructions expect that the applocation is installed under /srv/aplus/a-plus/
.
-
Install packges
sudo apt-get install nginx
-
Configure NGINX
if [ -d /etc/nginx/sites-available ] && grep -qs sites-enabled /etc/nginx/nginx.conf; then dest=/etc/nginx/sites-available/$(hostname).conf sudo ln -s ../sites-available/$(hostname).conf /etc/nginx/sites-enabled/$(hostname).conf else dest=/etc/nginx/conf.d/$(hostname).conf fi sed -e "s/__HOSTNAME__/$(hostname)/g" /srv/aplus/a-plus/doc/nginx/aplus-nginx.conf \ | sudo tee "$dest" > /dev/null openssl dhparam -out /etc/nginx/dhparams.pem 4096
NOTE: If you are going to use shibboleth, then use file nginx/aplus-nginx-shib.conf.
-
Create systemd service files for uWSGI processes
sudo cp /srv/aplus/a-plus/doc/nginx/aplus-*-uwsgi.service \ /etc/systemd/system sudo systemctl daemon-reload sudo systemctl enable aplus-web-uwsgi.service aplus-api-uwsgi.service sudo systemctl start aplus-web-uwsgi.service aplus-api-uwsgi.service
If you prefer to use Gunicorn, then you can use nginx/aplus-gunicorn.service.
-
Reload NGINX
sudo systemctl restart nginx.service
This guide bases on NGINX module nginx-http-shibboleth. This module uses fastcgi and shibboleth scripts to provide similar integration as Apache 2 plugin. As of March 2022, the latest version of libnginx-mod-http-headers-more-filter is only compatible up to NGINX version 1.18.0 and needs to be built manually for more recent versions of NGINX.
-
Starting from Ubuntu Bionic or NGINX 1.11 we can use dynamic modules
# as root cd /usr/src # edit /etc/apt/sources.list if necessary, make sure to enable the # -updates repository to get the latest version apt-get install build-essential devscripts apt-get source nginx apt-get build-dep nginx git clone https://github.com/nginx-shib/nginx-http-shibboleth.git git clone https://github.com/openresty/headers-more-nginx-module pushd nginx-1.*/ # The module has to be configured using the same arguments as nginx NGINX_CONFIGURE_FLAGS=$(nginx -V 2>&1 | grep configure\ arguments | \ sed 's/configure arguments: //') eval $(echo ./configure \ --add-dynamic-module=../nginx-http-shibboleth \ --add-dynamic-module=../headers-more-nginx-module \ $NGINX_CONFIGURE_FLAGS) make modules -j$(nproc) mkdir -p /usr/local/lib/nginx/modules chmod 644 \ objs/ngx_http_shibboleth_module.so \ objs/ngx_http_headers_more_filter_module.so cp objs/ngx_http_shibboleth_module.so \ objs/ngx_http_headers_more_filter_module.so \ /usr/local/lib/nginx/modules mkdir -p /etc/nginx/modules-available echo "load_module /usr/local/lib/nginx/modules/ngx_http_shibboleth_module.so;" > \ /etc/nginx/modules-available/50-mod-http-shibboleth.conf echo "load_module /usr/local/lib/nginx/modules/ngx_http_headers_more_filter_module.so;" > \ /etc/nginx/modules-available/50-mod-http-headers-more.conf ln -s ../modules-available/50-mod-http-shibboleth.conf \ /etc/nginx/modules-enabled/50-mod-http-shibboleth.conf ln -s ../modules-available/50-mod-http-headers-more.conf \ /etc/nginx/modules-enabled/50-mod-http-headers-more.conf systemctl restart nginx
Obsolete: With Ubuntu xenial or before NGINX 1.11
With Ubuntu xenial (16.04) and before NGINX 1.11, dynamic modules are not supported, so you need to rebuild the whole NGINX package.
sudo -i cd /usr/src apt-get install build-essential devscripts apt-get source nginx apt-get build-dep nginx git clone https://github.com/nginx-shib/nginx-http-shibboleth.git pushd nginx-1.*/ # add shib module to be build: debian/rules patch -l -p1 <<PATCH --- a/debian/rules +++ b/debian/rules @@ -98,6 +98,8 @@ --with-mail \\ --with-mail_ssl_module \\ --with-threads \\ + --add-module=\$(MODULESDIR)/headers-more-nginx-module \\ + --add-module=/usr/src/nginx-http-shibboleth/ \\ --add-module=\$(MODULESDIR)/nginx-auth-pam \\ --add-module=\$(MODULESDIR)/nginx-dav-ext-module \\ --add-module=\$(MODULESDIR)/nginx-echo \\ @@ -126,6 +128,7 @@ --with-stream_ssl_module \\ --with-threads \\ --add-module=\$(MODULESDIR)/headers-more-nginx-module \\ + --add-module=/usr/src/nginx-http-shibboleth/ \\ --add-module=\$(MODULESDIR)/nginx-auth-pam \\ --add-module=\$(MODULESDIR)/nginx-cache-purge \\ --add-module=\$(MODULESDIR)/nginx-dav-ext-module \\ PATCH # Create debian release dch -lshib "Add Shibboleth" dch -r "" # build debian packages dpkg-buildpackage -uc -us popd # install the packages dpkg -i $(ls nginx-common_*shib*_all.deb | sort | tail -n1) \ $(ls nginx-full_*shib*_amd64.deb | sort | tail -n1) exit # exit sudo session
-
Get NGINX supporting files
cd /etc/nginx wget https://raw.githubusercontent.com/nginx-shib/nginx-http-shibboleth/master/includes/shib_clear_headers
-
Install required packages
sudo apt-get install \ shibboleth-sp-common \ shibboleth-sp-utils \ xmltooling-schemas
-
Create systemd services and sockets for shibboleth scripts
sudo cp /srv/aplus/a-plus/doc/nginx/shib*.socket \ /srv/aplus/a-plus/doc/nginx/shib*.service \ /etc/systemd/system sudo systemctl daemon-reload sudo systemctl enable shibauthorizer.socket shibresponder.socket sudo systemctl start shibauthorizer.socket shibresponder.socket
-
Configure Shibboleth for your federation or organization
Configuration is under directory
/etc/shibboleth
. For HAKA, there is nginx/shibboleth2.xml. -
Shibboleth configuration in The
local_settings.py
Map your federations variables to ones used in A+. Most of the values are common, so only defining
PREFIX
should be enough. Currently,STUDENT_DOMAIN
is a required variable, as A+ presumes student numbers to be from a single domain. Rest of the options are documented insettings.py
.sudo tee -a /srv/aplus/a-plus/aplus/local_settings.py << EOF # Shibboleth SHIBBOLETH_ENVIRONMENT_VARS = { 'PREFIX': 'HTTP_SHIB_', 'STUDENT_DOMAIN': 'example.com', # XXX: change this! } EOF
-
Reload shibboleth
sudo systemctl restart shibd.service
-
Create or set a superuser.
It is not typically needed to create local superuser, thus you can skip the first part.
sudo -Hu aplus sh -c "cd /srv/aplus/a-plus; ../venv/bin/python3 ./manage.py createsuperuser"
Instead, it is recommended to setup Shibboleth or Social Auth and login your first admin. After that, you can make an existing user a superuser:
sudo -Hu aplus sh -c "cd /srv/aplus/a-plus; ../venv/bin/python3 ./manage.py set_superuser"
NOTE: You might need to use
--first-name
,--last-name
or--email
to limit the number of users. -
Create privacy notice, accessibility statement and support page.
Copy privacy notice, accessibility statement and support page templates.
sudo -Hu aplus sh -c " mkdir -p /srv/aplus/a-plus/local_templates/ cp /srv/aplus/a-plus/templates/privacy_notice_* \ /srv/aplus/a-plus/local_templates/ cp /srv/aplus/a-plus/templates/institution_accessibility_text* \ /srv/aplus/a-plus/local_templates/ cp /srv/aplus/a-plus/templates/support_channels* \ /srv/aplus/a-plus/local_templates/ "
Edit your local templates in
/srv/aplus/a-plus/local_templates/
to match your local information. If you want to translate the content to different languages, you can use language suffixes in the filenames. For example, you can create the following files for the local support page in English and Finnish.local_templates/support_channels_en.html
local_templates/support_channels_fi.html
A+ can be integrated with the organisation's Student Information System (SIS,
a system that manages information on courses and its participants).
For example, Aalto University, along with a few other Finnish universities
use "SISU" for this purpose. The integration with the organisation-specific
SIS system is done using a SIS plugin. For example, Aalto's SISU plugin
can be found in GitHub.
The plugin must implement
the interface StudentInfoSystem
.
If a SIS plugin is used, and after it is installed, two variables need to be set in local settings to enable it in A+, as in the following example:
SIS_PLUGIN_MODULE = 'course.sis_test'
SIS_PLUGIN_CLASS = 'SisTest'
Variable SIS_PLUGIN_MODULE
specifies the Python module path name of the plugin,
and SIS_PLUGIN_CLASS
is the name of the class implementing the generic
SIS interface. The proper values for these, along with the plugin installation
instructions, should be described in the plugin's documentation. See the
Aalto SISU plugin and its README file
for an example. In addition to these parameters, a plugin may have its own
plugin-specific additional parameters, as should be described in the plugin
installation instructions.
There is an option to periodically update enrollment information from SIS using Celery and Redis. Redis can be installed from dockerhub as a dedicated container. For local testing, for example, the following can be added to docker-compose.yml:
redis:
image: redis:latest
command: redis-server --save 60 1 --loglevel warning
expose:
- 6379
Periodic updates can be enabled by setting variable SIS_ENROLL_SCHEDULE
in
the Django settings. For example, the following activates periodic enrollment every
night at 1:00. More information about configuring periodic tasks can be found in
the Celery documentation.
from celery.schedules import crontab
SIS_ENROLL_SCHEDULE = crontab(hour=1, minute=0)
If this variable is not set, enrollments are not done automatically, but can still be triggered manually by the teacher if SIS is in use.