The following are instructions to set up a production server for MarkUs.
The following steps are for installation on a machine running Ubuntu 20.04 and all examples given below assume that you are installing in such an environment. Some changes may be required if installing on other operating systems.
For security reasons, MarkUs should be run as a dedicated user. Either designate an existing user to run MarkUs or create one. In this demo we will assume that MarkUs is being run as a user named markus
created by running:
useradd -m markus
The following commands should be run as the user designated or created in the Create User section
Choose where you want the MarkUs source code to be downloaded and cd
to that directory. This directory should be writeable by the markus
user (the one created in the create user section).
git clone https://github.com/MarkUsProject/Markus.git
cd Markus
git checkout release
Install system dependencies:
Note that on ubuntu 20.04 the default nodejs version is less than the required node version for MarkUs (version 18). In order to install the correct version of nodejs, first run the node_setup.sh
script:
curl https://deb.nodesource.com/setup_18.x -o node_setup.sh
bash node_setup.sh
sudo apt-get install --no-install-recommends ./markus_1.0_all.deb
rm node_setup.sh
The following ubuntu packages are optional:
- If you would like to enable optical character recognition for scanned exams and/or jupyter notebook rendering:
- python3
- python3-venv (required if you'd like to install python packages in a virtual environment)
- python3-dev
Install bundler as a system gem:
gem install bundler -v 2.3.17
Update the default configuration options for imagemagick so that it will allow reading pdf files:
sed -ri 's/(rights=")none("\s+pattern="PDF")/\1read\2/' /etc/ImageMagick-6/policy.xml
Install Ruby dependencies using bundler
./bin/bundle install --deployment --without development test offline
npm ci
Skip this step if you do not want to enable optical character recognition for scanned exams or jupyter notebook rendering.
We recommend installing python packages for MarkUs in a virtual environment since that will keep the python dependencies distinct from other python packages that you might have installed on your system.
If you are using a virtual environment, make sure to point MarkUs to the location of the python executable in that environment by setting the python:
configuration option in settings.local.yml
python: /path/to/the/venv/bin/python3
If this is not set, then the python3
executable that can be found in the PATH
will be used (if it exists).
pip install -r requirements-scanner.txt
If these dependencies are installed, it will enable automatic matching of student papers using optical character recognition.
pip install -r requirements-jupyter.txt
If these dependencies are installed, MarkUs will render jupyter notebook files as html (converted using nbconvert), otherwise it will render them as plain text.
For information on how to configure the MarkUs settings visit the Configuration page. Remember that any paths that you specify in the settings files should be readable/writable by the markus
user.
MarkUs is a Rails application and therefore requires a secret key to run in production mode (this key encrypts the application secrets, if any are used). Before continuing, run the following command (with the editor of your choice... vim is just an example):
EDITOR=vim ./bin/rails credentials:edit
to generate a secret key and encrypt any credentials. For more details, see the Rails Guide
MarkUs needs to know how to access the database. Database settings should be stored in config/database.yml
. For detailed instructions on how to configure this file see the Rails Guides.
For example, let's say your postgres instance was running on localhost, on port 5432, and you can connect as user mypostgresuser
with the password verysecret
. Also, you want MarkUs to use a database named markusdb
. In this case, you would write the following to the config/database.yml
file:
production:
adapter: postgresql
host: localhost
port: 5432
username: mypostgresuser
password: verysecret
database: markusdb
Note that everything is nested under production:
because MarkUs will run in production mode.
Now that the database settings have been set, create and migrate the database with the following commands:
RAILS_ENV=production ./bin/bundle exec rails db:create
RAILS_ENV=production ./bin/bundle exec rails db:migrate
MarkUs will run a lot faster in production if assets are precompiled. To precompile all static assets run the following command:
RAILS_ENV=production ./bin/bundle exec rails assets:precompile
MarkUs will run a puma web server on localhost but to make it accessible on the internet you will need to run your own web server (such as apache or nginx).
Here are a few things to consider when setting up the configuration files for your web server (all examples are for Apache configuration, you will have to look up the nginx equivalent if necessary):
-
If you have enabled the
rails.force_ssl
configuration option you should make sure that you have a working ssl certificate for your domain. Certbot has instruction on how to set this up. -
If you have enabled remote authentication make sure that:
- the user name value is forwarded in the request header as the "HTTP_X_FORWARDED_USER" key
- authentication is optional: since MarkUs supports both local and remote authentication, we don't want to force a user to use the remote authentication option if they can also log in with the local option.
For example, you might have the following snippet in your apache config file when using shibboleth for remote authentication, making all routes under '/' optionally protected by shibboleth authentication and forwarding the "user" variable from the shibboleth authetication to the header:
<Location "/"> Require shibboleth AuthType shibboleth RequestHeader set X-Forwarded-User %{user}e </Location>
Then in the configuration file set the url for shibboleth logins:
remote_auth_login_url: https://my.host.com/Shibboleth.sso/Login
MarkUs stores student submissions in bare repositories on disk, if your MarkUs instance is using git repositories and you would like your users to be able to clone/pull/push to those git repositories from the command line (or local git client), you need to set up access to the git repos over SSH or HTTPS.
To access git repositories over ssh, first make sure that your server can work as an ssh server by installing the openssh-server
package (or similar) and making sure that port 22 is accessible over the internet.
Then, ensure that key storage is enabled so that users can upload their public keys to MarkUs by setting the following [configuration setting]((./Configuration.md#markus-settings):
enable_key_storage: true
Then, you should set up the markus
user (that you created previously) so that users can ssh to your server as that user but only so that they can access git repositories. Make sure you follow the next steps very carefully. You do not want to accidentally allow users to ssh to your server and be able to perform arbitrary commands!
-
change the ownership of the
lib/repo/authorized_key_command.sh
file to the user that runs thesshd
process (usually this is root) and make it executable:chown root:root lib/repo/authorized_key_command.sh chmod u=rwx lib/repo/authorized_key_command.sh
-
make sure the
lib/repo/markus-git-shell.sh
script is executable (should be owned by themarkus
user already) and in themarkus
user's PATH:chmod u=rwx lib/repo/markus-git-shell.sh ln -s /app/lib/repo/markus-git-shell.sh /usr/local/bin/markus-git-shell.sh # for example, assuming /usr/local/bin is in the markus users's PATH
-
create a
.ssh/
directory for themarkus
user:mkdir /home/markus/.ssh # or wherever the user's home directory is located
-
set some environment variables that will be used by the
authorized_key_command.sh
script by writing the following to the/home/markus/.ssh/rc
file:export MARKUS_LOG_FILE=/path/to/some/logfile.log export MARKUS_REPO_LOC_PATTERN='/some/path/to/repos/' export GIT_SHELL=/usr/bin/git-shell
- where
/path/to/some/logfile.log
is an absolute path to a text file where you want the log output to be written (this is optional) - where
/some/path/to/repos/
is an absolute path to the location of the git repositories specified in thefile_storage.repos
configuration setting. - where
/usr/bin/git-shell
is a path to the git-shell executable installed by git. See the git documentation for more details
If multiple MarkUs instances are running on your machine using relative url roots, then you can specify multiple repository locations for each instance. The
MARKUS_REPO_LOC_PATTERN
variable can contain an(instance)
substring which will be replaced by the relative url root of the requested repository. For example, if you have two MarkUs instances running with relative url roots beingcsc108/
andcsc209/
and repositories for each at:/some/directory/markus/csc108/data/prod/repos/
/some/directory/markus/csc209/data/prod/repos/
Then setting
MARKUS_REPO_LOC_PATTERN=/some/directory/markus/(instance)/data/prod/repos
will mean that requests for repositories at either instance will be discovered properly. - where
-
update the sshd settings so that when users ssh as the
markus
user they will only be allowed to run git commands (and only on repositories that they have access to as determined by the.access
file or the check_repo_permissions function (see Git over HTTPS for details). Append the following to the/etc/ssh/sshd_config
file:Match User markus PermitRootLogin no AuthorizedKeysFile none AuthorizedKeysCommand /path/to/markus/root/lib/repo/authorized_key_command.sh AuthorizedKeysCommandUser markus
- where
/path/to/markus/root/
is the path to the root of the MarkUs source code so that/path/to/markus/root/lib/repo/authorized_key_command.sh
points to theauthorized_key_command.sh
script. You may have to change theauthorized_key_command.sh
file to be owned by the user that is running the sshd process (usually root). Feel free to also move theauthorized_key_command.sh
file elsewhere on disk if that is more convenient.
- where
-
start (or restart) the
sshd
process so that the new configuration settings get picked up -
Make sure that the
repository.ssh_url
configuration option is set up so that users will see the correct url to access their git repositories. For example, if your server is accessible over ssh asmy.server.com
, you should set the following configuration:repository: ssh_url: 'ssh://markus@my.server.com'
so that users can clone repositories from urls like
ssh://markus@my.server.com/blah/group1.git
-
Make sure that the
markus-git-shell.sh
script can check repository permissions. This can be done in one of two ways:-
Using the
.access
file: this method is deprecated but can still be used by setting theMARKUS_USE_ACCESS_FILE
environment variable to a truthy value in the/home/markus/.ssh/rc
file. -
Using the
check_repo_permissions
function: this method is preferred. It requires that the markus user be able to access the database used by your markus instance. In order to do this, create a.pg_service.conf
file in the markus user's home directory that contains access credentials. For example:[markus] host=postgres port=5432 user=mypostgresuser dbname=markusdb password=verysecret
The
markus-git-shell.sh
will look up the correct service to use to connect to the database in the following manner:-
if your MarkUs instance uses a relative url root then the service name should be the same as the relative url root.
For example, if your Markus instance is running at the url
my.server.com/something
then the service name in the.pg_service.conf
should be[something]
.Note that this means that the urls that clients will use to access the repositories will also include this relative url root (ie. the
ssh_url
configuration setting should be set to something like:'ssh://markus@my.server.com/something'
) -
if your MarkUs instance does not use a relative url root, the service name should be
[markus]
. If you would like to change this service name then you need to set theDEFAULT_SERVICE
environment variable to the new name in the/home/markus/.ssh/rc
file.
-
-
To access git repositories over https use the git-http-backend program.
We also recommend restricting access to git repositories by creating your own external authorization script to ensure that only authorized users can access the git repositories.
An example Apache configuration might look like:
<Location /git/ >
Require valid-user
AuthType Basic
AuthBasicProvider external
AuthExternal authorizegit
AuthName "MarkUs Git Repository"
SetEnv GIT_PROJECT_ROOT /some/path/to/repos
SetEnv GIT_HTTP_EXPORT_ALL
</Location>
ScriptAlias /git/ /usr/lib/git-core/git-http-backend/
(where you have previously set up an external authorization script called authorizegit in your Apache configuration)
Your authorization script should first authenticate the user and then it can check whether the user is authorized to view a given git repository using either the check_repo_permissions.rb
script or the .access
file (deprecated):
When setting up authorization protocols for this access, your authorization script should check who has permission to access which git repos by calling the check_repo_permissions
postgresql function. This function takes 3 arguments:
- the user name of the user requesting access to the instance
- the course name
- the repository name
For example:
echo "SELECT check_repo_permissions(:'user_name', :'course_name', :'repo_name')" | psql -qtA -v user_name=student123 -v course_name=csc108 -v repo_name=somerepo
When setting up autorization protocols for this access, your authorization script should check who has permission to which git repos by inspecting the .access
file in the repository storage directory (see the configuration settings).
Each row of this file is a comma delimited and contains a relative path from the repository storage directory to a specific repository on disk followed by a list of user names of people who have access to this repository. For example, if file_storage.repos
is /some/path/to/repos/
and the .access
file contains:
blah/group1.git,user1,user2
blah/group2.git,user1
bleh/*,user3
Then user1 should have permission to access repos at /some/path/to/repos/blah/group1.git
and /some/path/to/repos/blah/group2.git
, user2 should only have access to the first one. And user3 should have access to all repos in the /some/path/to/repos/bleh/
directory.
Finally, make sure that the repository.url
configuration option is set up so that users will see the correct url to access their git repositories.
For example, if your server's domain is https://my.host.com
and you're using the example apache configuration above, you should set the following configuration:
repository:
url: 'https://my.host.com/git'
So that users will be prompted to clone their repositories from urls like: https://my.host.com/git/blah/group1.git
Running MarkUs requires 3 processes which can be started with the following commands (run as the markus
user):
-
to start the Rails server (runs the main MarkUs process):
RAILS_ENV=production ./bin/bundle exec rails server -e production
-
to start resque (runs background jobs):
RAILS_ENV=production QUEUES=* ./bin/bundle exec rails environment resque:work
-
to start resque scheduler (schedules background jobs for later execution):
RAILS_ENV=production QUEUES=* ./bin/bundle exec rails environment resque:scheduler
If your server's domain is https://my.host.com
but you want to make MarkUs available at a relative root like https://my.host.com/markus/
for example then add this as an environment variable when starting each of the processes (above). For example, to start the Rails server you would run instead:
RAILS_RELATIVE_URL_ROOT=/markus RAILS_ENV=production ./bin/bundle exec rails server -e production
Make sure that your web server configuration is updated to reflect this url as well as any configuration options that reference the url.
Users can be added and updated using the API but only as the admin user. To get the admin user's api key, run the following rake task:
RAILS_ENV=production ./bin/rails db:admin
Courses can be added and updated using the API but only as the admin user. To get the admin user's api key, run the following rake task:
RAILS_ENV=production ./bin/rails db:admin
The autotester can be installed seperately from MarkUs on a different server (or the same one if you'd prefer). Here are the autotester Installation instructions.
Once the autotester has been set up, you must register each course that uses autotesting by providing the URL of the autotester through the API using the api/courses/<course_id>/update_autotest_url
route.
Note that MarkUs requires autotester version v2.3+