Installing the site
Installing the prerequisites
$ apt update
$ apt install git gcc g++ make python3-dev python3-pip python3-venv libxml2-dev libxslt1-dev zlib1g-dev gettext curl redis-server pkg-config libpq-dev
Install Node.js 26 using nvm:
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash
$ source ~/.bashrc
$ nvm install 26
$ nvm use 26
$ node --version
Creating the database
Next, we will set up the database using PostgreSQL. Install PostgreSQL if you haven't already.
$ apt update
$ apt install postgresql postgresql-client libpq-dev
The next step is to set up the database itself. You should execute the commands listed below to create the necessary database and user.
$ sudo -u postgres psql
postgres> CREATE USER freateoj WITH PASSWORD '<postgresql user password>';
postgres> CREATE DATABASE freateoj OWNER freateoj;
postgres> GRANT ALL PRIVILEGES ON DATABASE freateoj TO freateoj;
postgres> \q
Installing prerequisites
Now that you are done, you can start installing the site. First, create a virtual environment and activate it. Here, we'll create a virtual environment named freateojsite.
$ python3 -m venv freateojsite
$ . freateojsite/bin/activate
You should see (freateojsite) prepended to your shell. Henceforth, (freateojsite) commands assume you are in the code directory, with the virtual environment active.
Note: The virtual environment will help keep the modules needed separate from the system package manager, and save you many headaches when updating. Read more about virtual environments in the Python documentation.
Now, fetch the site source code:
(freateojsite) $ git clone --recursive https://github.com/freatevietnam/freateoj.git site
(freateojsite) $ cd site
Install Python dependencies into the virtual environment.
(freateojsite) $ pip3 install -r requirements.txt
Install Node.js packages:
(freateojsite) $ npm install
You will now need to configure dmoj/local_settings.py. You should make a copy of this sample settings file and read through it, making changes as necessary. Most importantly, you will want to update PostgreSQL credentials.
Note: Leave debug mode on for now; we'll disable it later after we've verified that the site works.
Generally, it's recommended that you add your settings in
dmoj/local_settings.pyrather than modifyingdmoj/settings.pydirectly.settings.pywill automatically readlocal_settings.pyand load it, so write your configuration there.
Sample local_settings.py
#####################################
########## Django settings ##########
#####################################
# See Django documentation
# for more info and help. If you are stuck, you can try Googling about
# Django - many of these settings below have external documentation about them.
#
# The settings listed here are of special interest in configuring the site.
# SECURITY WARNING: keep the secret key used in production secret!
# You may use this command to generate a key:
# python3 -c 'from django.core.management.utils import get_random_secret_key;print(get_random_secret_key())'
SECRET_KEY = 'This key is not very secure and you should change it.'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True # Change to False once you are done with runserver testing.
# Uncomment and set to the domain names this site is intended to serve.
# You must do this once you set DEBUG to False.
#ALLOWED_HOSTS = ['oj.freate.io.vn']
# Optional apps that DMOJ can make use of.
INSTALLED_APPS += (
)
# Caching. You can use memcached or redis instead.
# Documentation: See Django documentation
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
},
}
# For production, use Redis instead of LocMemCache to share cache across uWSGI workers:
# CACHES = {
# 'default': {
# 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
# 'LOCATION': 'redis://localhost:6379/1',
# },
# }
# Your database credentials. PostgreSQL is required.
# Documentation: See Django documentation
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'dmoj',
'USER': 'dmoj',
'PASSWORD': '<postgresql user password>',
'HOST': '127.0.0.1',
'PORT': 5432,
},
}
# Sessions.
# Documentation: See Django documentation
#SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
# Internationalization.
# Documentation: See Django documentation
LANGUAGE_CODE = 'vi'
DEFAULT_USER_TIME_ZONE = 'Asia/Ho_Chi_Minh'
USE_I18N = True
USE_L10N = True
USE_TZ = True
## django-compressor settings, for speeding up page load times by minifying CSS and JavaScript files.
# Documentation: See django-compressor documentation
COMPRESS_OUTPUT_DIR = 'cache'
COMPRESS_CSS_FILTERS = [
'compressor.filters.css_default.CssAbsoluteFilter',
'compressor.filters.cssmin.CSSMinFilter',
]
COMPRESS_JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter']
COMPRESS_STORAGE = 'compressor.storage.GzipCompressorFileStorage'
STATICFILES_FINDERS += ('compressor.finders.CompressorFinder',)
#########################################
########## Email configuration ##########
#########################################
# See Django documentation
# for more documentation. You should follow the information there to define
# your email settings.
# Use this if you are just testing.
#EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# The following block is included for your convenience, if you want
# to use Gmail.
#EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
#EMAIL_USE_TLS = True
#EMAIL_HOST = 'smtp.gmail.com'
#EMAIL_HOST_USER = '<your account>@gmail.com'
#EMAIL_HOST_PASSWORD = '<your password>'
#EMAIL_PORT = 587
# To use Mailgun, uncomment this block.
# You will need to run `pip install django-mailgun-mime` to get `MailgunBackend`.
#EMAIL_BACKEND = 'django_mailgun_mime.backends.MailgunMIMEBackend'
#MAILGUN_API_KEY = '<your Mailgun access key>'
#MAILGUN_DOMAIN_NAME = '<your Mailgun domain>'
# You can also use SendGrid, with `pip install sendgrid-django`.
#EMAIL_BACKEND = 'sgbackend.SendGridBackend'
#SENDGRID_API_KEY = '<Your SendGrid API Key>'
# The DMOJ site is able to notify administrators of errors via email,
# if configured as shown below.
# A tuple of (name, email) pairs that specifies those who will be mailed
# when the server experiences an error when DEBUG = False.
ADMINS = (
('Your Name', 'freatevietnam@gmail.com'),
)
# The sender for the aforementioned emails.
SERVER_EMAIL = 'FreateOJ: Freate Online Judge <freateoj@freate.io.vn>'
################################################
########## Static files configuration ##########
################################################
# See Django documentation.
# Change this to somewhere more permanent, especially if you are using a
# webserver to serve the static files. This is the directory where all the
# static files DMOJ uses will be collected to.
# You must configure your webserver to serve this directory as /static/ in production.
STATIC_ROOT = '/tmp/static'
# URL to access static files.
#STATIC_URL = '/static/'
# Uncomment to use hashed filenames with the cache framework.
#STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
############################################
########## DMOJ-specific settings ##########
############################################
## DMOJ site display settings.
SITE_NAME = 'FreateOJ'
SITE_FULL_URL = 'https://oj.freate.io.vn'
SITE_LONG_NAME = 'FreateOJ: Freate Online Judge'
SITE_ADMIN_EMAIL = 'freatevietnam@gmail.com'
TERMS_OF_SERVICE_URL = '//oj.freate.io.vn/tos/' # Use a flatpage.
## Media files settings.
# This is the directory where all the media files are stored.
# Change this to somewhere more permanent.
# You must configure your webserver to serve this directory in production.
MEDIA_ROOT = '/tmp/media'
## Problem data settings.
# This is the directory where all the problem data are stored.
# Change this to somewhere more permanent.
DMOJ_PROBLEM_DATA_ROOT = '/tmp/problem_data/'
## Bridge controls.
# The judge connection address and port; where the judges will connect to the site.
# You should change this to something your judges can actually connect to
# (e.g., a port that is unused and unblocked by a firewall).
BRIDGED_JUDGE_ADDRESS = [('localhost', 9999)]
# The bridged daemon bind address and port to communicate with the site.
#BRIDGED_DJANGO_ADDRESS = [('localhost', 9998)]
## DMOJ features.
# Set to True to enable full-text searching for problems.
ENABLE_FTS = False
# Set of email providers to ban when a user registers, e.g., {'throwawaymail.com'}.
BAD_MAIL_PROVIDERS = set()
# The number of submissions that a staff user can rejudge at once without
# requiring the permission 'Rejudge a lot of submissions'.
# Uncomment to change the submission limit.
#DMOJ_SUBMISSIONS_REJUDGE_LIMIT = 10
## Event server.
# Uncomment to enable live updating.
#EVENT_DAEMON_USE = True
# Socket.IO event daemon HTTP POST URL (for Django to send events to the daemon)
#EVENT_DAEMON_POST = 'http://localhost:9996/'
# Public URL for clients to connect to the Socket.IO server (through nginx reverse proxy)
#EVENT_DAEMON_GET = 'http://<your domain>/'
#EVENT_DAEMON_GET_SSL = 'https://<your domain>/'
#EVENT_DAEMON_POLL = '/channels/'
# If you would like to use the AMQP-based event server from See event-server documentation,
# uncomment this section instead. This is more involved, and recommended to be done
# only after you have a working event server.
#EVENT_DAEMON_AMQP = '<amqp:// URL to connect to, including username and password>'
#EVENT_DAEMON_AMQP_EXCHANGE = '<AMQP exchange to use>'
## Celery
#CELERY_BROKER_URL = 'redis://localhost:6379'
#CELERY_RESULT_BACKEND = 'redis://localhost:6379'
## CDN control.
# Base URL for a copy of Ace editor.
# Should contain ace.js, along with mode-*.js.
ACE_URL = '//cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/'
JQUERY_JS = '//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js'
SELECT2_JS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js'
SELECT2_CSS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css'
# A map of Earth in equirectangular projection, for timezone selection.
# Please try not to hotlink this poor site.
TIMEZONE_MAP = '<URL to a world map image>'
## Camo (See Camo documentation) usage.
#DMOJ_CAMO_URL = '<URL to your camo install>'
#DMOJ_CAMO_KEY = '<The CAMO_KEY environmental variable you used>'
# Domains to exclude from being camo'd.
#DMOJ_CAMO_EXCLUDE = ('https://oj.freate.io.vn',)
# Set to True to use https when dealing with protocol-relative URLs.
# See protocol-relative URL documentation for what they are.
#DMOJ_CAMO_HTTPS = False
# HTTPS level. Affects <link rel='canonical'> elements generated.
# Set to 0 to make http URLs canonical.
# Set to 1 to make the currently used protocol canonical.
# Set to 2 to make https URLs canonical.
#DMOJ_HTTPS = 0
## PDF rendering settings.
# Enable PDF generation.
#DMOJ_PDF_PDFOID_URL = '<URL to your pdfoid install>.'
# Directory to cache the PDF.
#DMOJ_PDF_PROBLEM_CACHE = '/home/dmoj-uwsgi/pdfcache'
# Path to use for nginx's X-Accel-Redirect feature.
# Should be an internal location mapped to the above directory.
#DMOJ_PDF_PROBLEM_INTERNAL = '/pdfcache'
## Data download settings.
# Uncomment to allow users to download their data.
#DMOJ_USER_DATA_DOWNLOAD = True
# Directory to cache user data downloads.
# It is the administrator's responsibility to clean up old files.
#DMOJ_USER_DATA_CACHE = '/home/dmoj-uwsgi/userdatacache'
# Path to use for nginx's X-Accel-Redirect feature.
# Should be an internal location mapped to the above directory.
#DMOJ_USER_DATA_INTERNAL = '/userdatacache'
# How often a user can download their data.
#DMOJ_USER_DATA_DOWNLOAD_RATELIMIT = datetime.timedelta(days=1)
# Uncomment to allow contest authors to download contest data
#DMOJ_CONTEST_DATA_DOWNLOAD = True
# Directory to cache contest data downloads.
# It is the administrator's responsibility to clean up old files.
#DMOJ_CONTEST_DATA_CACHE = '/home/dmoj-uwsgi/contestdatacache'
# Path to use for nginx's X-Accel-Redirect feature.
# Should be an internal location mapped to the above directory.
#DMOJ_CONTEST_DATA_INTERNAL = '/contestdatacache'
# How often contest data can be exported.
# This applies per contest, not per user.
#DMOJ_CONTEST_DATA_DOWNLOAD_RATELIMIT = datetime.timedelta(days=1)
## ======== Logging Settings ========
# Documentation: See Django documentation
# See Python logging documentation
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'file': {
'format': '%(levelname)s %(asctime)s %(module)s %(message)s',
},
'simple': {
'format': '%(levelname)s %(message)s',
},
},
'handlers': {
# You may use this handler as an example for logging to other files.
'bridge': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '<desired bridge log path>',
'maxBytes': 10 * 1024 * 1024,
'backupCount': 10,
'formatter': 'file',
},
'mail_admins': {
'level': 'ERROR',
'class': 'dmoj.throttle_mail.ThrottledEmailHandler',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'file',
},
},
'loggers': {
# Site 500 error mails.
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False,
},
# Judging logs as received by bridged.
'judge.bridge': {
'handlers': ['bridge', 'mail_admins'],
'level': 'INFO',
'propagate': True,
},
# Catch all logs to stderr.
'': {
'handlers': ['console'],
},
# Other loggers of interest. Configure at will.
# - judge.user: logs naughty user behaviours.
# - judge.problem.pdf: PDF generation log.
# - judge.html: HTML parsing errors when processing problem statements etc.
# - judge.mail.activate: logs for the reply to activate feature.
# - event_socket_server
},
}
## ======== Integration Settings ========
## Python Social Auth
# Documentation: See python-social-auth documentation
# You can define these to enable authentication through the following services.
#SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ''
#SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ''
#SOCIAL_AUTH_FACEBOOK_KEY = ''
#SOCIAL_AUTH_FACEBOOK_SECRET = ''
#SOCIAL_AUTH_GITHUB_SECURE_KEY = ''
#SOCIAL_AUTH_GITHUB_SECURE_SECRET = ''
## ======== Custom Configuration ========
# You may add whatever Django configuration you would like here.
# Do try to keep it separate so you can quickly patch in new settings.
Compiling assets
FreateOJ uses sass and autoprefixer to generate the site stylesheets. FreateOJ comes with a make_style.sh script that may be run to compile and optimize the stylesheets.
(freateojsite) $ ./make_style.sh
Now, collect static files into STATIC_ROOT as specified in dmoj/local_settings.py.
(freateojsite) $ ./manage.py collectstatic
You will also need to generate internationalization files.
(freateojsite) $ ./manage.py compilemessages
(freateojsite) $ ./manage.py compilejsi18n
Setting up Celery
The FreateOJ uses Celery workers to perform most of its heavy lifting, such as batch rescoring submissions. We will use Redis as its broker, though note that other brokers that Celery supports will work as well.
Start up the Redis server, which is needed by the Celery workers.
$ service redis-server start
Configure local_settings.py by uncommenting CELERY_BROKER_URL and CELERY_RESULT_BACKEND. By default, Redis listens on localhost port 6379, which is reflected in local_settings.py. You will need to update the addresses if you changed Redis's settings.
We will test that Celery works soon.
Setting up database tables
We must generate the schema for the database, since it is currently empty.
(freateojsite) $ ./manage.py migrate
Next, load some initial data so that your install is not entirely blank.
(freateojsite) $ ./manage.py loaddata navbar
(freateojsite) $ ./manage.py loaddata language_small
(freateojsite) $ ./manage.py loaddata demo
Warning: Keep in mind that the
demofixture creates a superuser account with a username and password ofadmin. If your site is exposed to others, you should change the user's password or remove the user entirely.
You should create an admin account with which to log in initially.
(freateojsite) $ ./manage.py createsuperuser
Running the server
Now, you should verify that everything is going according to plan.
(freateojsite) $ ./manage.py check
At this point, you should attempt to run the server, and see if it all works.
(freateojsite) $ ./manage.py runserver 0.0.0.0:8000
You should Ctrl-C to exit after verifying.
Warning: Do not use
runserverin production!We will set up a proper webserver using nginx and uWSGI soon.
You should also test to see if bridged runs.
(freateojsite) $ ./manage.py runbridged
If there are no errors after about 10 seconds, it probably works. You should Ctrl-C to exit.
Next, test that the Celery workers run.
(freateojsite) $ celery -A dmoj_celery worker
You can Ctrl-C to exit.
Setting up uWSGI
runserver is insecure and not meant for production workloads, and should not be used beyond testing. In the rest of this guide, we will be installing uwsgi and nginx to serve the site, using supervisord to keep site and bridged running. It's likely other configurations may work, but they are unsupported.
First, copy our uwsgi.ini (link). You should change the paths to reflect your install.
You need to install uwsgi.
(freateojsite) $ pip3 install uwsgi
To test, run:
(freateojsite) $ uwsgi --ini uwsgi.ini
If it says workers are spawned, it probably works. You should Ctrl-C to exit.
Sample uwsgi.ini
[uwsgi]
# Socket and pid file location/permission.
uwsgi-socket = /tmp/dmoj-site.sock
chmod-socket = 666
pidfile = /tmp/dmoj-site.pid
# You should create an account dedicated to running dmoj under uwsgi.
#uid = dmoj-uwsgi
#gid = dmoj-uwsgi
# Paths.
chdir = <dmoj repo dir>
pythonpath = <dmoj repo dir>
virtualenv = <virtualenv path>
# Details regarding DMOJ application.
protocol = uwsgi
master = true
env = DJANGO_SETTINGS_MODULE=dmoj.settings
module = dmoj.wsgi:application
optimize = 2
# Scaling settings. Tune as you like.
memory-report = true
cheaper-algo = backlog
cheaper = 3
cheaper-initial = 5
cheaper-step = 1
cheaper-rss-limit-soft = 201326592
cheaper-rss-limit-hard = 234881024
workers = 7
Setting up supervisord
You should now install supervisord and configure it.
$ apt install supervisor
Copy our site.conf (link) to /etc/supervisor/conf.d/site.conf, bridged.conf (link) to /etc/supervisor/conf.d/bridged.conf, celery.conf (link) to /etc/supervisor/conf.d/celery.conf and fill in the fields.
Next, reload supervisord and check that the site, bridged, and celery have started.
$ supervisorctl update
$ supervisorctl status
If all three processes are running, everything is good! Otherwise, peek at the logs and see what's wrong.
Sample site.conf
[program:site]
command=<path to virtualenv>/bin/uwsgi --ini uwsgi.ini
directory=<path to site>
stopsignal=QUIT
stdout_logfile=/tmp/site.stdout.log
stderr_logfile=/tmp/site.stderr.log
Sample bridged.conf
[program:bridged]
command=<path to virtualenv>/bin/python manage.py runbridged
directory=<path to site>
stopsignal=INT
# You should create a dedicated user for the bridged to run under.
user=<user to run under>
group=<user to run under>
stdout_logfile=/tmp/bridge.stdout.log
stderr_logfile=/tmp/bridge.stderr.log
Sample celery.conf
[program:celery]
command=<path to virtualenv>/bin/celery -A dmoj_celery worker
directory=<path to site>
# You should create a dedicated user for celery to run under.
user=<user to run under>
group=<user to run under>
stdout_logfile=/tmp/celery.stdout.log
stderr_logfile=/tmp/celery.stderr.log
Sample wsevent.conf
[program:wsevent]
command=/usr/bin/node <site repo path>/websocket/daemon.js
environment=NODE_PATH="<site repo path>/node_modules"
# Should create a dedicated user.
user=<username>
group=<username>
stdout_logfile=/tmp/wsevent.stdout.log
stderr_logfile=/tmp/wsevent.stderr.log
Setting up nginx
Now, it's time to set up nginx.
$ apt install nginx
You should copy the sample nginx.conf (link), edit it and place it in wherever it is supposed to be for your nginx install.
Note: Typically,
nginxsite files are located in/etc/nginx/conf.d. Some installations might place it at/etc/nginx/sites-availableand require a symlink in/etc/nginx/sites-enabled.
Next, check if there are any issues with your nginx setup.
$ nginx -t
If not, reload the nginx configuration.
Sample nginx.conf
server {
listen 80;
listen [::]:80;
# Change port to 443 and do the nginx ssl stuff if you want it.
# Change server name to the HTTP hostname you are using.
# You may also make this the default server by listening with default_server,
# if you disable the default nginx server declared.
server_name <hostname>;
add_header X-UA-Compatible "IE=Edge,chrome=1";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
charset utf-8;
try_files $uri @icons;
error_page 502 504 /502.html;
location ~ ^/502\.html$|^/logo\.png$|^/robots\.txt$ {
root <site code path>;
}
location @icons {
root <site code path>/resources/icons;
error_page 403 = @uwsgi;
error_page 404 = @uwsgi;
}
location @uwsgi {
uwsgi_read_timeout 600;
# Change this path if you did so in uwsgi.ini
uwsgi_pass unix:///tmp/dmoj-site.sock;
include uwsgi_params;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
}
location /static {
gzip_static on;
expires max;
root <django setting STATIC_ROOT, without the final /static>;
# Comment out root, and use the following if it doesn't end in /static.
#alias <STATIC_ROOT>;
}
location /martor {
root <django setting MEDIA_ROOT>;
}
location /pdf {
root <django setting MEDIA_ROOT>;
}
location /submission_file {
root <django setting MEDIA_ROOT>;
}
# Uncomment if you are using PDFs and want to serve it faster.
# This location name should be set to DMOJ_PDF_PROBLEM_INTERNAL.
#location /pdfcache {
# internal;
# root <the value of DMOJ_PDF_PROBLEM_CACHE in local_settings.py>;
#
# # Default from local_settings.py:
# #root /home/dmoj-uwsgi/;
#}
# Uncomment if you are allowing user data downloads and want to serve it faster.
# This location name should be set to DMOJ_USER_DATA_INTERNAL.
#location /userdatacache {
# internal;
# root <path to data cache directory, without the final /userdatacache>;
#
# # Default from local_settings.py:
# #root /home/dmoj-uwsgi/;
#}
# Uncomment if you are allowing contest data downloads and want to serve it faster.
# This location name should be set to DMOJ_CONTEST_DATA_INTERNAL.
#location /contestdatacache {
# internal;
# root <path to data cache directory, without the final /contestdatacache>;
#
# # Default from local_settings.py:
# #root /home/dmoj-uwsgi/;
#}
# Socket.IO event server (default port 9996)
#location /socket.io/ {
# proxy_pass http://127.0.0.1:9996/socket.io/;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# proxy_read_timeout 86400;
#}
# Long-poll fallback for event server
#location /channels/ {
# proxy_read_timeout 120;
# proxy_pass http://127.0.0.1:9996/channels/;
#}
}
$ service nginx reload
You should be good to go. Visit the site at where you set it up to verify.
If it does not work, check nginx logs and uwsgi log stdout/stderr for details.
Note: Now that your site is installed, remember to set
DEBUGtoFalseinlocal_settings. Leaving itTrueis a security risk.
Configuration of event server
The event server uses Socket.IO for real-time updates. The daemon is a Node.js process that listens for HTTP POST requests from Django and broadcasts events to connected browser clients.
Configure in local_settings.py:
EVENT_DAEMON_USE = True
EVENT_DAEMON_POST = 'http://localhost:9996/' # Django posts events here
EVENT_DAEMON_GET = 'http://<your domain>/' # Browser connects here (through nginx)
EVENT_DAEMON_POLL = '/channels/' # Long-poll fallback path
Copy wsevent.conf (link) to /etc/supervisor/conf.d/wsevent.conf, update the paths, then restart supervisor.
The Socket.IO daemon runs on a single port (default 9996) and handles:
- WebSocket/long-poll connections from browser clients
- HTTP POST requests from Django to broadcast events
$ supervisorctl update
$ supervisorctl restart bridged
$ supervisorctl restart site
$ service nginx restart
Updating the site
The FreateOJ is under active development, so occasionally you may wish to update. This is a fairly simple process.
Warning: The FreateOJ development team makes no commitment to backwards compatibility. It's possible that an update migration might add, change, or delete data from your install. Always back up before attempting an update.
If in doubt, feel free to contact us on Discord.
First, switch to the site virtual environment, and pull the latest changes.
(freateojsite) $ git pull origin master
Dependencies may have changed since the last time you updated, so install any missing ones now.
(freateojsite) $ pip3 install -r requirements.txt
The database schema might also have changed, so update it.
(freateojsite) $ ./manage.py migrate
(freateojsite) $ ./manage.py check
Finally, update any static files that may have changed.
(freateojsite) $ ./make_style.sh
(freateojsite) $ ./manage.py collectstatic
(freateojsite) $ ./manage.py compilemessages
(freateojsite) $ ./manage.py compilejsi18n
That's it! You may wish to condense the above steps into a script you can run at a later time.
Contest formats
The FreateOJ ships with 6 contest formats by default: Default, IOI, Codechef IOI Ranklist (henceforth shortened to simply Legacy IOI), ECOO, AtCoder, and ICPC.
Default
The Default contest format is what all contests ran on pre-April 23, 2019.
The score is the sum of the highest-scoring submission on each problem, and ties are broken by adding the time of the last submission to each problem with a non-zero maximum score.
Note that any submission will increase time penalty, not just score-changing submissions.
There are no additional options that can be configured for this contest format.
IOI
The IOI contest format emulates the scoring used by IOI.
The score is equal to the sum of the final score on each problem, where the final score for a problem is the maximum score for each subtask across all submissions, and by default, ties are not broken. For example, consider a contestant that makes two submissions on a task with two subtasks. The first gets 30 points on the first subtask and 10 points on the second subtask. The second gets 0 points on the first subtask and 40 points on the second subtask. The final score for this problem will be 70.
The cumtime option can be set to true within the JSON configuration. This will break ties by summing the submission times of the first submissions that pass each subtask.
Legacy IOI
The Legacy IOI contest format emulates the scoring used by Codechef's IOI Ranklist.
The score is equal to the sum of the highest-scoring submission on each problem, and by default, ties are not broken.
The cumtime option can be set to true within the JSON configuration. This will break ties by summing the submission times of the most recent total score-changing submissions.
ECOO
The ECOO contest format is based on the scoring system used by the ECOO contest.
The score is equal to the sum of scores of the last submission to each problem. By default, ties are not broken, however, setting cumtime to true will sum the times of the last submission to each problem, and use that for tiebreaking.
The first_ac_bonus field, as suggested by the name, will add the specified number of points to a problem's score if it is solved on the first attempt, excluding compile errors and internal errors. This field defaults to 10.
The time_bonus field awards a bonus for solving problems faster. The field is in minutes, and for each such interval of time before the contest ends, any submission with a non-zero score will have a bonus point added. This field defaults to 5. Note that specifying 0 will disable this bonus.
For example, say a submission with a score of 50/100 is submitted 23 minutes before the contest ends. ⌊23/5⌋ = 4, so 4 bonus points are awarded, giving a total score of 50 + 4 = 54 for that problem.
AtCoder
The AtCoder contest format is based on the contest format used by AtCoder.
The score is equal to the sum of the highest-scoring submission on each problem, and ties are broken based on the time of the last score-changing submission plus the penalty.
The penalty is specified by the penalty field, and defaults to 5 minutes. The penalty is equal to the total number of incorrect submissions prior to the highest-scoring submission on each solved problem, multiplied by the specified value, in minutes, and is added to the cumulative time.
ICPC
The ICPC contest format is based on the contest format used by the ICPC.
The score is equal to the number of problems solved, and ties are broken firstly based on the sum of the elapsed time that a correct submission was submitted to each problem plus the penalty, and secondly based on the time of the last score-changing submission.
The penalty is specified by the penalty field, and defaults to 20 minutes. The penalty is equal to the number of incorrect submissions prior to the first correct submission, multiplied by the specified value, in minutes, and is added to the cumulative time. Note that the time penalty is applied to all problems with a non-zero score (this format will not automatically disable partial points).
Permission system
The FreateOJ's permission system is very extensive and allows fine-tuning a user's permissions. Here, we will document which permissions are required to perform certain tasks on the site. Any undocumented models means that they follow Django's default permission system of (can_add_<model>, can_change_<model>, can_delete_<model>, and can_view_<model>).
Blog posts
Blog posts follow Django's default permission system.
edit_all_post (Edit all posts)
Prerequisite permissions: change_blogpost
The user can edit all blog posts on the admin site.
Comments
Comments and comment locks follow Django's default permission system.
override_comment_lock (Override comment lock)
The user can post comments on pages which are comment locked.
Contests
Contest participations, contest problems, contest submissions, and contest tags follow Django's default permission system.
see_private_contest (See private contests)
The user can see all contests without explicitly set as organizer. The user will also be able to see hidden scoreboards. However, they will not be able to edit contests on the admin site.
edit_own_contest (Edit own contests)
The user can edit contests on the admin site, but only if they are explicitly set as organizer.
edit_all_contest (Edit all contests)
Prerequisite permissions: edit_own_contest
Superseded permissions: see_private_contest
The user can see and edit all contests on the admin site without explicitly set as organizer.
clone_contest (Clone contest)
The user can clone contests that they can edit on the admin site.
moss_contest (MOSS contest)
The user can run MOSS on contests that they can edit on the admin site.
contest_rating (Rate contests)
The user can edit rating-related fields on the contest admin page, and rate the contest.
contest_access_code (Contest access codes)
The user can edit the access_code field on the contest admin page.
create_private_contest (Create private contests)
The user can create organization-private and user-private contests. The user can also edit the is_visible field on the contest admin page as long as the contest is organization-private or user-private.
change_contest_visibility (Change contest visibility)
The user can edit the is_visible field on the contest admin page.
contest_problem_label (Edit contest problem label script)
The user can edit the contest problem label script on the contest admin page. The contest problem label script is a LUA script for customizing the header for each problem on the scoreboard.
lock_contest (Change lock status of contest)
The user will be able to lock and unlock submissions from a contest. Note that this permission does not require the lock_submission permission. See the lock_submission permission for more details on what locking a submission entails.
Organizations
Organizations follow Django's default permission system.
organization_admin (Administer organizations)
The user can edit registrant, admins, is_open, slots fields on the admin site.
edit_all_organization (Edit all organizations)
Prerequisite permissions: change_organization
The user can edit all organizations.
Problems
see_organization_problem (See organization-private problems)
The user can see organization-private (but public) problems.
see_private_problem (See hidden problems)
Superseded permissions: see_organization_problem
The user can see all problems. However, they will not be able to edit problems on the admin site or view submission source code.
edit_own_problem (Edit own problems)
The user can edit problems on the admin site, but only if they are explicitly set as author or curator. They will also be able to view submission source code for these problems.
edit_public_problem (Edit public problems)
Prerequisite permissions: edit_own_problem
The user can edit problems on the admin site, but only if the problem is publicly visible. Note that this includes all problems which are marked as public, regardless of whether they are only public to specific organizations or everyone. They will also be able to view submission source code for these problems.
edit_all_problem (Edit all problems)
Prerequisite permissions: edit_own_problem
Superseded permissions: see_private_problem, edit_public_problem, view_all_submission
The user can see and edit all problems on the admin site.
problem_full_markup (Edit problems with full markup)
The user will be able to edit the description of problems which offer full markup. Full markup includes access to all HTML tags, including the <script> and <style> tags. Without this permission, the user will only be able to edit problems whose description offers a safe, limited subset of HTML tags.
clone_problem (Clone problem)
The user can clone problems that they can edit.
change_public_visibility (Change is_public field)
The user can change the is_public field.
change_manually_managed (Change is_manually_managed field)
The user can change the is_manually_managed field.
Problem solutions
Problem solutions follow Django's default permission system.
see_private_solution (See hidden solutions)
The user can see all problem solutions for problems they can access, regardless of if the solution is public or not.
Profile
Profiles follow Django's default permission system.
totp (Edit TOTP settings)
The user can see and edit TOTP-related fields, such as a user's TOTP key.
Submissions
Submission visibility and editability are determined by problem permissions. If the user can edit the problem, they can also edit related submissions. No user can add submissions, and deleting permissions follow Django's default permission system.
abort_any_submission (Abort any submission)
The user can abort any submission. Without this permission, the user can only abort submissions that they have submitted and which have not been rejudged.
rejudge_submission (Rejudge the submission)
Prerequisite permissions: edit_own_problem
The user can rejudge submissions for problems they can edit.
rejudge_submission_lot (Rejudge a lot of submissions)
Prerequisite permissions: rejudge_submission
The user can batch-rejudge submissions, and bypass the FREATEOJ_SUBMISSIONS_REJUDGE_LIMIT setting.
spam_submission (Submit without limit)
The user can bypass the FREATEOJ_SUBMISSION_LIMIT setting, meaning they can have an infinite number of non-rejudged submissions queued.
view_all_submission (View all submission)
The user can view submissions for all problems, but cannot edit them on the admin site.
resubmit_other (Resubmit others' submission)
The user can resubmit submissions by other users.
lock_submission (Change lock status of submission)
The user will be able to lock and unlock submissions. Locked submissions will not be rejudgeable by anyone (including superusers) until they are unlocked.
Managing problems through the site interface
The FreateOJ comes with an online interface for creating and editing problem statements as well as data. This guide is intended as an introduction to using these features for creating your own problems.
Configuring site-managed data
Set FREATEOJ_PROBLEM_DATA_ROOT to a folder of your choice. The test data for all problems with site-managed data will be stored within this folder.
Adding a problem
To start, head to the admin site and use your credentials to log in. Once there, click the Add button on the Problems menu.
This will open up the main problem editor. To start, you should provide a problem code (must be unique site-wide), and a title for your problem. Make sure to mark yourself as an author, as otherwise you will be locked out of your problem.
Here you may edit your problem statement. The FreateOJ features a rich Markdown-based syntax, with custom extensions for LaTeX-based display math, and Mathjax-based inline math. See the sample problem markdown file for a full feature example (you may copy/paste its content into your editor).
There are many options controlling your problem described in the editor, that you may use to customize the execution of your problem.
Once you are done preparing your statement, click the Save button, then scroll up to the top of the page and click the View on site button.
Editing test data
Internally, the FreateOJ uses a YAML-based format for describing problem data, which you may read about in the problem format documentation. The site provides an interface for managing problem data, removing the need to drop down to YAML configuration for most problems.
On the problem page, click the Edit test data link to open up the test data editor.
In the editor, you must first upload a zip archive containing the input/output data used for your problem. The typical convention is to use text files ending with a .in extension for input files, and .out for output files, with the test case number embedded in the filename.
For example, for a problem with a code of testp1, the first test case would be named testp1.1.in, with an output file testp1.1.out.
Using this format is not necessary — the judge will accept any filenames — but using it will allow the test data editor to autocomplete paths, saving some manual input.
There are many other options, but for most problems, only one more is necessary: the per-case point value. If partial points are enabled in the problem statement editor, then a user's score on the problem is equal to the sum of the point values of the cases they got right divided by the total sum of case point values, multiplied by the number of points the problem is worth.
For example, if your problem is worth 100 points and has 3 cases weighted 1/2/7 points respectively, a user who gets the first two cases correct and then fails the last one will have a score of 30 points, out of 100.
Submitting to a problem
After you have created your test data, you should head back to the problem and click the Submit solution button. If at any point in time you need to update your data, you may do so from the test data editor, and it will update automatically.
API
!?> Untested on FreateOJ
FreateOJ supports a simple JSON API for accessing most data used by the backend. Access to the API makes use of API tokens.
API tokens
FreateOJ supports API tokens for accessing the majority of the site as your native user. The admin portion of the site is left intentionally inaccessible with these tokens. You may generate an API token on your Edit profile page. To use, include the following header with every request where <API Token> is your API token:
Authorization: Bearer <API Token>
Error responses
The following error codes may be returned by the API token authentication layer. Note that the site itself may return other codes not listed here or identical codes with different error messages, so read the error messages carefully.
400 Invalid authorization header- The header you provided is invalid. Make sure it matches the following regex:Authorization: Bearer ([a-zA-Z0-9_-]{48})401 Invalid token- The token you provided is invalid. Make sure it matches the one on your Edit profile page.403 Admin inaccessible- You are trying to access the inaccessible admin portion of the site.
Rate limiting
90 requests per minute
If you exceed this limit, you will be captcha'd. Captchas are automatically removed after 3 days. However, note that if you are captcha'd again within this 3 day period, the 3 day counter will reset.
Note: This is only a feature on FreateOJ.
Format
All responses are of the following structure:
{
"api_version": "2.0",
"method": "<HTTP method that was used>",
"fetched": "<time that the request was made in ISO format>",
"data": "<rest of the data>",
"error": "<any errors that were encountered>"
}
It is guaranteed that only one of data or error will be in the response.
Error format
{
"error": {
"code": "<HTTP status code>",
"message": "<error message>"
}
}
Data format
The data format differs depending on the endpoint called. For endpoints that respond with a single object:
{
"data": {
"object": "<object data>"
}
}
For endpoints that respond with a list of objects:
{
"data": {
"current_object_count": "<number of objects in the current page>",
"objects_per_page": "<maximum number of objects that will ever appear on a single page>",
"total_objects": "<total number of objects in the list>",
"page_index": "<the current page's index, one indexed>",
"total_pages": "<total number of pages>",
"objects": [
"<list of object data>"
]
}
}
Filtering
Most of the API endpoints support filtering via query parameters. There are two types of filtering that are supported, basic filtering and list filtering. Basic filtering allows filtering for a single value, while list filtering allows filtering for a group of values. Each endpoint describes the filtering that it supports, with the name in a codeblock being the query parameter name.
Example of basic filtering: /api/v2/problems?partial=True - This will only return problems with partial points enabled.
Example of list filtering: /api/v2/problems?organization=1&organization=2&type=Implementation - This will only return problems (private to organizations 1 OR 2) AND (problem type is Implementation).
Endpoints
/api/v2/contests
Example: /api/v2/contests?tag=seasonal&tag=dmopc
Basic filters
is_rated- boolean
List filters
tag- tag nameorganization- organization id
Object response
{
"key": "<contest key>",
"name": "<contest name>",
"start_time": "<contest start time in ISO format>",
"end_time": "<contest end time in ISO format>",
"is_rated": "<whether the contest is rated>",
"rate_all": "<whether the contest is rated on join>",
"time_limit": "<contest time limit in seconds, or null if the contest is not windowed>",
"tags": ["<list of tag name>"]
}
/api/v2/contest/<contest key>
Example: /api/v2/contest/bts19
Object response
{
"key": "<contest key>",
"name": "<contest name>",
"start_time": "<contest start time in ISO format>",
"end_time": "<contest end time in ISO format>",
"time_limit": "<contest time limit in seconds, or null if the contest is not windowed>",
"is_rated": "<whether the contest is rated>",
"rate_all": "<whether the contest is rated on join>",
"has_rating": "<whether the contest has been rated>",
"rating_floor": "<the minimum user rating required for the user to be rated>",
"rating_ceiling": "<the maximum user rating for the user to be rated>",
"hidden_scoreboard": "<whether the contest's scoreboard is hidden>",
"scoreboard_visibility": "<whether the scoreboard is (V)isible, visible after (C)ontest, or visible after (P)articipation>",
"is_organization_private": "<whether the contest is private to organizations>",
"organizations": ["<list of organization id>"],
"is_private": "<whether the contest is private to specific users>",
"tags": ["<list of tag name>"],
"format": {
"name": "<the name of the contest format>",
"config": "<the contest format JSON configuration>"
},
"problems": [
{
"points": "<the integer amount of points the problem is worth in contest>",
"partial": "<whether it is possible to achieve partial points on the problem>",
"is_pretested": "<whether the problem is pretested>",
"max_submissions": "<the maximum number of submissions allowed, or null if there is no limit>",
"label": "<the label for this problem>",
"name": "<problem name>",
"code": "<problem code>"
}
],
"rankings": [
{
"user": "<participant username>",
"start_time": "<effective participation start time in ISO format>",
"end_time": "<participation end time in ISO format>",
"score": "<participant score>",
"cumulative_time": "<participant cumulative time, dependent on the contest format>",
"tiebreaker": "<participant tiebreaker value>",
"old_rating": "<participant rating before the contest, or null if not rated>",
"new_rating": "<participant rating after the contest, or null if not rated>",
"is_disqualified": "<whether this participant is disqualified>",
"solutions": ["<list of contest format-dependent dictionaries for individual problem scores>"]
}
]
}
/api/v2/participations
Example: /api/v2/participations?contest=dmopc19c6&virtual_participation_number=0&is_disqualified=True
Basic filters
contest- contest keyuser- user usernameis_disqualified- booleanvirtual_participation_number- non-negative integer
Object response
{
"user": "<participant username>",
"contest": "<contest key>",
"start_time": "<effective participation start time in ISO format>",
"end_time": "<participation end time in ISO format>",
"score": "<participant score>",
"cumulative_time": "<participant cumulative time, dependent on the contest format>",
"tiebreaker": "<participant tiebreaker value>",
"is_disqualified": "<whether this participant is disqualified>",
"virtual_participation_number": "<virtual participation number>"
}
/api/v2/problems
Example: /api/v2/problems?partial=True&type=Uncategorized
Basic filters
partial- boolean
List filters
group- problem group full nametype- problem type full nameorganization- organization id
Additional filters
search- similar to a list filter, except searches for the list of parameters in the problem's name, code, and description.
Object response
{
"code": "<problem code>",
"name": "<problem name>",
"types": ["<list of type full name>"],
"group": "<problem group full name>",
"points": "<problem points>",
"partial": "<whether partials are enabled for this problem>",
"is_organization_private": "<whether the problem is private to organizations>",
"is_public": "<whether the problem is publicly visible>"
}
/api/v2/problem/<problem code>
Example: /api/v2/problem/helloworld
Object response
{
"code": "<problem code>",
"name": "<problem name>",
"authors": ["<list of author username>"],
"types": ["<list of type full name>"],
"group": "<problem group full name>",
"time_limit": "<problem time limit>",
"memory_limit": "<problem memory limit>",
"language_resource_limits": [
{
"language": "<language key>",
"time_limit": "<language-specific time limit>",
"memory_limit": "<language-specific memory limit>"
}
],
"points": "<problem points>",
"partial": "<whether partials are enabled for this problem>",
"short_circuit": "<whether short circuit is enabled for this problem>",
"languages": ["<list of language key>"],
"is_organization_private": "<whether the problem is private to organizations>",
"organizations": ["<list of organization id>"],
"is_public": "<whether the problem is publicly visible>"
}
Additional info
is_public: Whether the problem is publicly visible to the organizations listed. If is_organization_private is false, the problem is visible to all users.
/api/v2/users
Example: /api/v2/users?organization=8
List filters
organization- organization id
Object response
{
"id": "<user id>",
"username": "<user username>",
"points": "<user points>",
"performance_points": "<user performance points>",
"problem_count": "<number of problems the user has solved>",
"rank": "<user display rank>",
"rating": "<user rating>"
}
/api/v2/user/<user username>
Example: /api/v2/user/Xyene
Object response
{
"id": "<user id>",
"username": "<user username>",
"points": "<user points>",
"performance_points": "<user performance points>",
"problem_count": "<number of problems the user has solved>",
"solved_problems": ["<list of problem code>"],
"rank": "<user display rank>",
"rating": "<user rating>",
"organizations": ["<list of organization id>"],
"contests": [
{
"key": "<contest key>",
"score": "<user score>",
"cumulative_time": "<user cumulative time, dependent on the contest format>",
"rating": "<user rating after this contest, or null if not rated>",
"raw_rating": "<user raw rating after this contest, or null if not rated>",
"performance": "<user performance, or null if not rated>"
}
]
}
/api/v2/submissions
Example: /api/v2/submissions?user=Ninjaclasher
Basic filters
user- user usernameproblem- problem code
List filters
language- language keyresult- string
Object response
{
"id": "<submission id>",
"problem": "<problem code>",
"user": "<user username>",
"date": "<submission date in ISO format>",
"language": "<language key>",
"time": "<submission time usage>",
"memory": "<submission memory usage>",
"points": "<submission points awarded>",
"result": "<submission result>"
}
/api/v2/submission/<submission id>
Example: /api/v2/submission/1000000
Object response
{
"id": "<submission id>",
"problem": "<problem code>",
"user": "<user username>",
"date": "<submission date in ISO format>",
"time": "<submission time usage>",
"memory": "<submission memory usage>",
"points": "<submission points awarded>",
"language": "<language key>",
"status": "<submission status>",
"result": "<submission result>",
"case_points": "<submission case points>",
"case_total": "<submission case total>",
"cases": ["<list of case or batch data>"]
}
Additional info
case or batch data: Each object will be one of the following, depending on whether the current case is a batch or a single test case:
Case data
{
"type": "case",
"case_id": "<case id>",
"status": "<case status>",
"time": "<case time usage>",
"memory": "<case memory usage>",
"points": "<case points awarded>",
"total": "<case total points>"
}
Batch data
{
"type": "batch",
"batch_id": "<batch id>",
"cases": ["<list of case data>"],
"points": "<batch points awarded>",
"total": "<batch total points>"
}
/api/v2/organizations
Example: /api/v2/organizations?is_open=False
Basic filters
is_open- boolean
Object response
{
"id": "<organization id>",
"slug": "<organization slug>",
"short_name": "<organization name>",
"is_open": "<whether anyone can join the organization>",
"member_count": "<number of users in the organization>"
}
/api/v2/languages
Example: /api/v2/languages?common_name=Python
Basic filters
common_name- language common name
Object response
{
"id": "<language id>",
"key": "<language key>",
"short_name": "<language short name>",
"common_name": "<language common name>",
"ace_mode_name": "<Ace mode name>",
"pygments_name": "<Pygments name>",
"code_template": "<default code template>"
}
/api/v2/judges
Example: /api/v2/judges
Object response
{
"name": "<judge name>",
"start_time": "<judge start time in ISO format>",
"ping": "<judge ping in milliseconds>",
"load": "<judge load>",
"languages": ["<list of language key>"]
}
Rendering LaTeX math in problem statements
Warning: Untested on FreateOJ
The FreateOJ platform is capable of rendering LaTeX math for constraints and formulas that may appear in problem statements.
The FreateOJ makes use of the Wikimedia Mathoid project to render math.
Installing Mathoid
Follow the installation instructions of Mathoid. Moving forward, we'll assume that you are running Mathoid on localhost:8888.
Configuring FreateOJ to use Mathoid
Assuming Mathoid is installed, configuring FreateOJ to generate math with it requires the addition of a few lines to local_settings.py.
# The URL Mathoid is running on.
MATHOID_URL = 'http://localhost:8888'
# A directory accessible by the user running Mathoid, as well as the web (nginx) user.
# For optimal performance, change this to something more persistent than /tmp.
MATHOID_CACHE_ROOT = '/tmp/mathoid_cache'
# The URL base MATHOID_CACHE_ROOT is configured to be served under in your webserver. For
# example, if /tmp/mathoid_cache/render.png exists, example.com/mathoid/render.png should
# serve it.
MATHOID_CACHE_URL = '//example.com/mathoid/'
Restart FreateOJ for the changes to take effect. After restarting, you may have to purge Django's cache before seeing any changes.
Using Mathoid math in problem statements
A snippet of a problem statement using Mathoid to render math is shown below.
The Fibonacci sequence is a well known sequence of numbers in which
$$F(n) = \begin{cases} 0, & \text{if } n = 0 \\ 1, & \text{if } n = 1 \\ F(n-2) + F(n-1), & \text{if } n \ge 2 \end{cases}$$
Given a number ~N~ ~(1 \le N \le 10^{19})~, find the ~N^{th}~ Fibonacci number, modulo ~1\,000\,000\,007~ ~(= 10^9 + 7)~.
**Note:** For 30% of the marks of this problem, it is guaranteed that ~(1 \le N \le 1\,000\,000)~.
Rendering LaTeX diagrams in problem statements
Warning: Untested on FreateOJ
The FreateOJ platform is capable of rendering LaTeX documents onto problem statements. This can be useful for things like drawing graphs with ease, porting over PDF resources, and so on.
FreateOJ supports this through a related project, Texoid. Texoid interfaces with texlive to provide a REST endpoint for LaTeX rendering.
Installing Texoid
First, clone the Texoid repository, and install it and its dependencies into a new virtualenv.
$ git clone Texoid repository
$ cd texoid
$ python3 -m venv env
$ . env/bin/activate
$ pip install -e .
Texoid relies on LaTeX distribution to render documents to DVI format, dvisvgm to convert to SVGs, and ImageMagick to convert SVGs into PNGs. On a typical Debian or Ubuntu machine, you can fetch everything you need with:
$ apt install texlive-latex-base texlive-binaries imagemagick
Running Texoid
To start the Texoid server, run:
$ export LATEX_BIN=<path to latex>
$ export DVISVGM_BIN=<path to dvisvgm>
$ export CONVERT_BIN=<path to convert>
$ env/bin/texoid --port=<port>
The environment variables are not necessary if all three executables are present in $PATH, as they should be if you followed the installation instructions above. Here, convert refers to ImageMagick's convert tool.
To test, start Texoid with --port=8886. Then, we can request a render of a simple LaTeX document.
\documentclass{standalone}
\begin{document}
$E=mc^2$
\end{document}
The response should contain JSON, with SVG and a Base64-encoded PNG inside.
$ curl --data 'q=%5Cdocumentclass%7Bstandalone%7D%0A%5Cbegin%7Bdocument%7D%0A%24E%3Dmc%5E2%24%0A%5Cend%7Bdocument%7D' http://localhost:8886
{
"success": true,
"svg": "<?xml version='1.0'?><svg...</svg>",
"png": "iVBORw0KGgoA...kSuQmCC\n",
"meta": {
"width": "48",
"height": "10"
}
}
Configuring FreateOJ to use Texoid
Once Texoid is installed, configuring FreateOJ to generate LaTeX diagrams with it requires the addition of a few lines to local_settings.py.
# The URL Texoid is running on.
TEXOID_URL = 'http://localhost:8886'
# A directory accessible by the user running Texoid, as well as the web (nginx)
# user.
#
# For optimal performance (since launching texlive is expensive), change this
# to something more persistent than /tmp.
TEXOID_CACHE_ROOT = '/tmp/texoid_cache'
# The URL base TEXOID_CACHE_ROOT is configured to be served under in your
# webserver. For example, if /tmp/texoid_cache/render.png exists,
# example.com/texoid/render.png should serve it.
TEXOID_CACHE_URL = '//example.com/texoid/'
Restart FreateOJ for the changes to take effect. After restarting, you may have to purge Django's cache before seeing any changes.
Using LaTeX diagrams in problem statements
To invoke Texoid to generate LaTeX diagrams, wrap your LaTeX code in <latex> blocks.
## This is a LaTeX Demo
The diagram below is **real LaTeX!**
<latex>
\documentclass{standalone}
\begin{document}
$E=mc^2$
\end{document}
</latex>
Typically, \documentclass{standalone} works best for inlining diagrams.
PDF generation of problem statements
The FreateOJ supports rendering problem statements to PDF. This can be useful in the case of on-site contests, where contestants receive paper versions of the problems.
PDF generation is backed by a related project, Pdfoid. Pdfoid interfaces with Selenium to provide a REST endpoint for PDF rendering.
Installing Pdfoid
First, clone the Pdfoid repository, and install it and its dependencies into a new virtualenv.
$ git clone Pdfoid repository
$ cd pdfoid
$ python3 -m venv env
$ . env/bin/activate
$ pip install -e .
Install exiftool, which is used to set PDF titles.
$ apt install exiftool
Install ChromeDriver, a special version of the Chromium engine needed by Selenium to create PDFs.
$ apt install chromium-driver
Running Pdfoid
To start the Pdfoid server, run:
$ export CHROME_PATH=<path to chrome>
$ export CHROMEDRIVER_PATH=<path to chromedriver>
$ export EXIFTOOL_PATH=<path to exiftool>
$ env/bin/pdfoid --port=<port>
The environment variables are not necessary if all three executables are present in $PATH, as they should be if you followed the installation instructions above.
To test, start Pdfoid with --port=8887. Then, we can request a render of a simple HTML document.
<div>Hello, World!</div>
The response should contain JSON, with a Base64-encoded PDF inside.
$ curl -d "title=Hello&html=Hello, World" -X POST -H "Content-Type: application/x-www-form-urlencoded" http://localhost:8887
{
"success": true,
"pdf": "JVBERi0xLjQKJdPr6eEKMSAwIG9iago8PC9DcmVhdG9yIChDaHJvbWl1bSkK..."
}
Note: The FreateOJ uses a Segoe UI font when viewed on Windows browsers. If running Pdfoid on a Linux server, installing Segoe UI fonts on it will provide optimal rendering quality — otherwise, a fallback font will be used and statements will look subpar.
Configuration
Configuring FreateOJ to generate PDFs with Pdfoid can be done by adding the following lines to your local_settings.py.
# The URL Pdfoid is running on.
FREATEOJ_PDF_PDFOID_URL = 'http://localhost:8887'
# Optional, cache location for generated PDFs. You should consider using
# something more persistent than /tmp, since PDF generation is an expensive
# operation. If omitted, no cache will be used.
FREATEOJ_PDF_PROBLEM_CACHE = '/tmp'
# Optional, URL serving FREATEOJ_PDF_PROBLEM_CACHE with X-Accel-Redirect. This is
# recommended to have nginx serve PDFs, rather than uWSGI. To enable this,
# uncomment the line below, as well as the corresponding section in the sample
# nginx configuration file.
#FREATEOJ_PDF_PROBLEM_INTERNAL = '/pdfcache'
Restart FreateOJ for the changes to take effect.
Troubleshooting
"View as PDF" button doesn't show up
If a "View as PDF" button does not show up on the problem page, make sure that the FREATEOJ_PDF_PDFOID_URL variable is set.
"View as PDF" button shows up
If a "View as PDF" button shows up, but generation fails, an error log should be displayed in the browser. This log will also be captured by the judge.problem.pdf Django log handler. Depending on the error, explicitly setting the Pdfoid environment variables CHROME_PATH to the path of the Chromium binary and CHROMEDRIVER_PATH to the path of the ChromeDriver binary may alleviate the problem.
For other errors, take a look at the Selenium documentation, specifically the common exceptions section.
reCAPTCHA spam registration prevention
Warning: Untested on FreateOJ
If you run FreateOJ for any prolonged period of time, eventually spambots will begin registering in large numbers.
FreateOJ can integrate with reCAPTCHA to filter out spam registrations with a little setup.
Getting an API key
First, head to the reCAPTCHA admin site at Google reCAPTCHA admin site. Select "reCAPTCHA v2", specify your domain, and click through to get an API key pair.
In local_settings.py, set RECAPTCHA_PUBLIC_KEY to the site key, and RECAPTCHA_PRIVATE_KEY to the secret key.
Installing reCAPTCHA support
First, install django-recaptcha2 in the site virtual environment.
(freateojsite) $ pip3 install django-recaptcha2
Finally, open local_settings.py in your editor of choice, and add snowpenguin.django.recaptcha2 to the end of INSTALLED_APPS.
Restart FreateOJ for the changes to take effect. You should now have an "I'm not a robot" checkbox on registration.
SSL proxying for user content
Warning: Untested on FreateOJ
User-generated content (e.g., comments) poses a threat to site security, and can cause mixed-content warnings. If your site is served over HTTPS, this may be suboptimal - routing user content through a secure server can help.
The FreateOJ site provides support for this through the Github Camo project, which requires CoffeeScript to be installed (apt install coffeescript).
Warning: Setting up Camo on the same server as your site can leave you open to attacks, even if you are set up behind Cloudflare: a malicious user can link an image to their domain, have Camo access it, and then view their server logs to see the requesting IP (allowing them to attack you behind e.g. Cloudflare).
If this is important in your scenario, consider running Camo on a separate server.
Installing Camo to /code
$ cd /code
$ git clone Camo repository camo
Now, Camo may be started by running /code/camo/server.coffee.
$ PORT="<port>" CAMO_KEY="<key>" coffee /code/camo/server.coffee
- Camo will listen on
<port>. <key>is the HMAC secret key used for digests. Set it to anything you want. This is used for cache-busting purposes, so it does not need to be secure.
Configuring FreateOJ to use Camo
To enable the use of Camo in the FreateOJ site, you need to specify a couple of variables in your local_settings.py.
# The URL on which Camo is listening
FREATEOJ_CAMO_URL = "https://example.com[:port]"
# The key you specified for running Camo
FREATEOJ_CAMO_KEY = "<key>"
# Domains to exclude from Camo proxying. Typically, these would be your own domains which you use
# for content delivery, and you know to already be secure.
FREATEOJ_CAMO_EXCLUDE = ("https://oj.freate.io.vn",)
# Whether Camo should use HTTPS for protocol neutral URIs (you probably want this)
FREATEOJ_CAMO_HTTPS = True
Restart FreateOJ for the changes to take effect. After restarting, you may have to purge Django's cache before seeing any changes.
Cache configuration
FreateOJ uses Django's cache framework to cache data. This section explains how to configure caching and manage cache versions.
Cache versioning
Cache versioning allows you to invalidate all cached data when deploying new code. This ensures users get fresh data after updates.
How it works:
KEY_PREFIX: A prefix added to all cache keys to avoid collisions with other appsVERSION: An integer that gets appended to cache keys. Increase this to invalidate all cache.
When to increment VERSION:
- After deploying code changes that affect cached data
- After database migrations that change cached models
- When you need to force a full cache refresh
Example configurations
Redis (Recommended for Production)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://localhost:6379/1',
'KEY_PREFIX': 'freateoj',
'VERSION': 1, # Increase to invalidate cache
},
}
Memcached
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
'KEY_PREFIX': 'freateoj',
'VERSION': 1,
},
}
Local Memory (Development Only)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'KEY_PREFIX': 'freateoj',
'VERSION': 1,
},
}
Deploy checklist
- Update
VERSIONin CACHES config - Deploy code
- (Optional) Run
python manage.py shell -c "from django.core.cache import cache; cache.clear()"to manually clear cache
Per-site cache keys
The project uses the following cache key patterns:
| Key Pattern | Purpose |
|---|---|
freateoj:1:* | Versioned cache entries (default) |
problem:* | Problem data cache |
contest:* | Contest data cache |
user:* | User profile cache |
When you increment VERSION, all keys prefixed with freateoj:1: become inaccessible, and new keys will use freateoj:2:.
Contest data downloads
The FreateOJ allows contest authors to download contest data. At the time of writing, only submission data can be downloaded.
By default, this feature is disabled. To enable, uncomment the relevant settings in local_settings.py.
# Uncomment to allow contest authors to download contest data
FREATEOJ_CONTEST_DATA_DOWNLOAD = True
# Directory to cache contest data downloads.
# It is the administrator's responsibility to clean up old files.
FREATEOJ_CONTEST_DATA_CACHE = '/home/freateoj-uwsgi/contestdatacache'
# Path to use for nginx's X-Accel-Redirect feature.
# Should be an internal location mapped to the above directory.
FREATEOJ_CONTEST_DATA_INTERNAL = '/contestdatacache'
# How often contest data can be exported.
# This applies per contest, not per user.
FREATEOJ_CONTEST_DATA_DOWNLOAD_RATELIMIT = datetime.timedelta(days=1)
Also, uncomment the relevant section in your Nginx configuration if you wish to take advantage of Nginx's X-Accel-Redirect feature (see nginx documentation).
# Uncomment if you are allowing contest data downloads and want to serve it faster.
# This location name should be set to FREATEOJ_CONTEST_DATA_INTERNAL.
location /contestdatacache {
internal;
root <path to data cache directory, without the final /contestdatacache>;
# Default from local_settings.py:
#root /home/freateoj-uwsgi/;
}
These data files are not cleaned up automatically. Although each contest can have at most one data file stored on the server at a time, you may want to clean up old files. A Cron job should suffice:
0 */4 * * * find /home/freateoj-uwsgi/contestdatacache/ -type f -mtime +2 -delete
This Cron job will delete files older than 2 days every 4 hours. You are recommended to tweak these values according to your ratelimit.
You should now find a link on your Edit Profile that allows you to download your data, along with various configuration options.
User data downloads
The FreateOJ allows users to download their data. At the time of writing, only user comment and submission data can be downloaded.
By default, this feature is disabled. To enable, uncomment the relevant settings in local_settings.py.
## Data download settings.
# Uncomment to allow users to download their data.
FREATEOJ_USER_DATA_DOWNLOAD = True
# Directory to cache user data downloads.
# It is the administrator's responsibility to clean up old files.
FREATEOJ_USER_DATA_CACHE = '/home/freateoj-uwsgi/userdatacache'
# Path to use for nginx's X-Accel-Redirect feature.
# Should be an internal location mapped to the above directory.
FREATEOJ_USER_DATA_INTERNAL = '/userdatacache'
# How often a user can download their data.
FREATEOJ_USER_DATA_DOWNLOAD_RATELIMIT = datetime.timedelta(days=1)
Also, uncomment the relevant section in your Nginx configuration if you wish to take advantage of Nginx's X-Accel-Redirect feature (see nginx documentation).
# Uncomment if you are allowing user data downloads and want to serve it faster.
# This location name should be set to FREATEOJ_USER_DATA_INTERNAL.
location /userdatacache {
internal;
root <path to data cache directory, without the final /userdatacache>;
# Default from local_settings.py:
#root /home/freateoj-uwsgi/;
}
These data files are not cleaned up automatically. Although each user can have at most one data file stored on the server at a time, you may want to clean up old files. A Cron job should suffice:
0 */4 * * * find /home/freateoj-uwsgi/userdatacache/ -type f -mtime +2 -delete
This cron job will delete files older than 2 days every 4 hours. You are recommended to tweak these values according to your ratelimit.
You should now find a link on your Edit profile that allows you to download your data, along with various configuration options.
Setting up a judge
This guide goes through the process of installing a judge and connecting it to the site. It is intended for Linux-based machines (WSL included); Windows is not supported.
It is assumed that the site installation instructions have been followed, and that a bridge instance is running.
Site-side setup
First, add a judge on the admin page, located under /admin/judge/. Provide the name of the judge and the authentication key for the judge. You may use the Regenerate button to generate an authentication key.
In local_settings.py, find the BRIDGED_JUDGE_ADDRESS. This is the address you will be connecting a judge to. By default, this should be localhost:9999. If you are connecting from a different machine, you will need to change localhost to an actual IP. Also, ensure that this port is open, or you will receive cryptic error messages when attempting to connect a judge.
Finally, ensure the bridge is running. You should see something similar to the following lines.
$ supervisorctl status
bridged RUNNING pid <pid>, uptime <uptime>
Judge-side setup
FreateOJ supports installing judges through Docker and a PyPI package. We recommend the Docker installation if you are able to use Docker, since we have dealt with the hard problem of getting many runtimes co-existing on the same machine and keeping them up-to-date. The PyPI package is also supported, and may give you more control at the expense of more administrative complexity.
With Docker
Pre-built
We maintain Docker images with all runtimes we support in the judge-server project.
Runtimes are split into three tiers of decreasing support. Tier 1 includes Python 2/3, C/C++ (GCC only), Java 8, and Pascal. Tier 3 contains all the runtimes we run on FreateOJ. Tier 2 contains some in-between mix; read the Dockerfile for each tier for details. These images are rebuilt and tested every week to contain the latest runtime versions.
Note: FreateOJ uses a custom tier,
tierfreateoj, which contains all the runtimes in Tier 1 and some additional ones. You can find the list of supported runtimes here. The Docker image is maintained at freateoj/judge-tierfreateoj on Docker Hub.
From source
The session below build a judge-tierfreateoj:
$ git clone --recursive judge-server
$ cd judge/.docker
$ make judge-tierfreateoj
The session below spawns a tierfreateoj judge image in the same server as the site server. It expects problems to be placed on the host under /mnt/problems, and judge-specific configuration to be in /mnt/problems/judge.yml.
Note: For first time developers: Both the judge and site can share a common problems folder, which is specified at
DMOJ_PROBLEM_DATA_ROOTinsettings.pyfor the site and as below for the judge.
Your judge.yml file should look something like below:
id: <judge name>
key: <judge authentication key>
problem_storage_globs:
- /problems/*
$ docker run \
--name judge \
--network="host" \
-v /mnt/problems:/problems \
--cap-add=SYS_PTRACE \
-d \
--restart=always \
freateoj/judge-tierfreateoj:latest \
run -p 9999 -c /problems/judge.yml localhost -A 0.0.0.0 -a 12345
If you changed the port that was specified in BRIDGED_JUDGE_ADDRESS of the site's local_settings.py, you need to change the -p 9999 to match the config as well.
If you want to run multiple judges, you need to changes:
- Container name (
--name judge): each judge need different name - judge.yml file (
/problems/judge.yml): each judge need different config file -a 12345: change to others ports
Through PyPI
Warning: Not available for FreateOJ
We are not maintaining our judge on PyPI, you should use the docker setup above.
Configuring a judge
The FreateOJ judge is configured with a YAML file, which contains the runtimes, problems folders, and other information.
A sample configuration file is available here.
Sample judge_conf.yml
# This is the same ID you specified in the judge admin panel.
id: ExampleJudge
# The key this judge will use to authenticate with the site server, generated from the admin panel.
key: "100/Base64/characters/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMN"
# Where to look for problems on the local filesystem.
# Any directory matching any of the following globs with an init.yml file is assumed to be a problem directory.
problem_storage_globs:
- /mnt/problems/folder1/* # matches /mnt/problems/folder1/<problem>
- /mnt/problems/folder2/**/ # matches any subfolder of /mnt/problems/folder2
- /mnt/problems/folder3/year20[0-9][0-9] # matches /mnt/problems/folder3/year2023, for example
# All configuration for language executors.
# If you're unsure of what values a language needs, your best bet is to consult the source of the executor:
# See judge-server executors
runtime:
ccl: /opt/ccl/lx86cl
sed: /bin/sed
awk: /usr/bin/awk
gnatmake: /usr/bin/gnatmake
chicken-csc: /usr/bin/csc
dart: /opt/dart-sdk/bin/dart
tclsh: /usr/bin/tclsh
cobc: /usr/bin/cobc
erl: /usr/bin/erl
erlc: /usr/bin/erlc
tprolog: /opt/turing/tprolog/tprolog
tprologc: /opt/turing/tprolog/tprologc
turing_dir: /opt/turing/tprolog
node: /usr/bin/node
lua: /usr/bin/lua
ocaml: /usr/bin/ocamlopt
pypy: /opt/pypy/bin/pypy
pypydir: /opt/pypy/
pypy3: /opt/pypy3/bin/pypy
pypy3dir: /opt/pypy3/
ghc: /usr/bin/ghc
go: /usr/bin/go
fsharpc: /usr/bin/fsharpc
mono-csc: /usr/bin/mono-csc
mono-vbnc: /usr/bin/vbnc
mono: /usr/bin/mono
dmd: /usr/bin/dmd
fpc: /opt/fpc-2.6.4/bin/fpc
clang: /usr/bin/clang
clang++: /usr/bin/clang++
gcc: /usr/bin/gcc
g++: /usr/bin/g++
g++11: /usr/bin/g++
g++14: /opt/gcc-5.1.0/bin/g++
gfortran: /usr/bin/gfortran
nasm: /usr/bin/nasm
ld: /usr/bin/ld
python: /opt/python27/bin/python2.7
python2dir: /opt/python27/
python3: /opt/python34/bin/python3.4
python3dir: /opt/python34/
java: /usr/bin/java
javac: /usr/bin/javac
java8: /usr/lib/jvm/jdk-8-oracle-i586/bin/java
javac8: /usr/lib/jvm/jdk-8-oracle-i586/bin/javac
# Custom V8 build to facilitate online judging: See V8 DMOJ documentation
v8dmoj: /home/judge/judge/executors/v8dmoj
php: /usr/bin/php
phpconfdir: /etc/php5/
perl: /usr/bin/perl
ruby19: /usr/bin/ruby1.9.1
ruby21: /usr/bin/ruby
racket: /opt/racket/bin/racket
raco: /opt/racket/bin/raco
racket-lib: /opt/racket/lib
gnustep-config: /usr/bin/gnustep-config
gobjc: /usr/bin/gcc
Runtimes
The runtimes are configured with a runtime node. While most runtimes can be automatically configured with dmoj-autoconf, those that are not in the $PATH variable will not be automatically detected, and will need to be manually configured.
Problems
The problems are configured with problem_storage_globs. This is a list of (potentially recursive) globs, as defined by Python's glob library. Any folders that match any of the listed globs and contain an init.yml will be treated as a problem directory. For example:
/mnt/problems/folder1/*will match/mnt/problems/folder1/problem1./mnt/problems/folder2/**/will match all subfolders of/mnt/problems/folder2, e.g./mnt/problems/folder2/foo/bar/problem2./mnt/problems/folder3/year20[0-9][0-9]will match/mnt/problems/folder3/year2023.
ID
The judge's display name is configured with an id node. This is a string, and it should match the one on the site interface.
Key
This key is used to validate your judge's connection to the bridge, and as such, should match the one on the site interface. This is configured with a key node.
Supported languages
FreateOJ supports grading in 56 languages: Ada, Assembly (x64/x86), AWK, Brain****, C (Clang/GCC), C#, C++14 (Clang/GCC), C++03/11/17/20, C11, COBOL, D, Dart, F#, Forth, Fortran, Go, Groovy, Haskell, INTERCAL, Java 8/latest, Kotlin, Lean 4, Lisp, LLVM IR, Lua, NASM, NASM64, OCaml, Pascal, Perl, PHP, Pike, Prolog, PyPy 2/3, Python 2/3, Racket, Ruby, Rust, Scala, Scheme, Sed, Swift, TCL, Text, Turing, V8 JavaScript, Visual Basic, Zig. All these languages are tested in production on FreateOJ.
As it stands, some languages are used more than others in the scope of competitive programming, so some executors have been tested more than others. As a result, they are more likely to be bug-free. As of October 2022, the stats are:
Java 8: 1047632
Python 3: 900484
C++11: 589256
C++14: 510588
C++17: 393980
Java (latest): 172589
C++20: 163351
Python 2: 142767
Status codes
This page lists all status codes encountered on the FreateOJ and their description. It should be noted that it is possible for a test case to be given multiple status codes (indeed, this is usually the case for non-AC verdicts), in which case the one with the highest priority will be displayed. This page lists status codes in order of increasing priority.
AC - Accepted
Your program passed testing! In some cases, this may be accompanied with additional feedback from the grader.
WA - Wrong Answer
Your program did not crash while executing, but the output it produced was wrong. As for AC, this may be accompanied with additional feedback stating what you did wrong.
IR - Invalid Return
Your program returned with a nonzero exit code (if you're not using a native language like C++, it crashed). For languages like Python or Java, this will typically be accompanied with the name of the exception your program threw, e.g., NameError or java.lang.NullPointerException, respectively.
RTE - Runtime Error
Your program caused a runtime error to occur. This will only occur for native languages like C or C++. FreateOJ maps many common RTEs to more useful descriptions, described below.
| Feedback | Description |
|---|---|
segmentation fault, bus error | Your program was killed by SIGSEGV or SIGBUS. Generally, this means you ran out of memory, but it can also mean that you are accessing arrays out of bounds, in some cases. |
floating point exception | Your program performed a bad arithmetic operation, such as division by zero. |
killed | Your program was killed by the runtime for some reason (we don't know). |
{} syscall disallowed | Unless you are doing something of a dubious nature, you should never see this message. If you do, please submit an issue on the judge-server repository so we can get it sorted out. It is raised when your program attempts to use a disallowed system call. |
std::bad_alloc | new failed to allocate enough memory. |
failed initializing | Your program uses too much data defined in global scope for it to fit inside the memory constraints at startup. A typical example is code like int arr[10000][10000] on a problem with a 64mb memory limit — the aforementioned array will take 381mb, far above the allowed limit. |
OLE - Output Limit Exceeded
Your program outputted too much data to stdout, typically over 256mb (though some problems may have custom — generally larger — constraints).
MLE - Memory Limit Exceeded
Your program ran out of memory. Sometimes, this might manifest itself as an RTE with segmentation fault or std::bad_alloc.
TLE - Time Limit Exceeded
Your program took too long to execute.
IE - Internal Error
If you see this, it means either the judge encountered an error or the problemsetter's configuration is incorrect.
Problem format
Each problem is stored in its own directory. That directory must contain a file named init.yml.
init.yml
The entire file is a YAML object. It must contain one key, test_cases. test_cases can either be a list of test cases, or two regexes to match input and output test cases. Optionally, but almost always, will there be an archive key, which allows the problem data to be stored, compressed, in a .zip file, instead of the problem directory as flat files.
test_cases
There are two methods to specify test cases.
The first method is to use a list of YAML associative arrays. Each element in the list is a YAML associative array (usually written as a keyed branch) that represents a test case. The element contains the key points, mapping to an integer specifying the number of points that test case is worth.
If points: 0 is specified, getting a non-AC verdict on this case will result in the remaining test cases being skipped. This also applies to batched test cases where points: 0 is specified.
However, note that these cases will not be automatically run at the beginning, that is if you specify
test_cases:
- {in: case1.1.in, out: case1.1.out, points: 100}
- {in: case1.0.in, out: case1.0.out, points: 0}
case1.1 will be graded before case1.0, and getting a non-AC verdict on case1.0 will result in a verdict of 100/100 (WA).
The correct configuration would be:
test_cases:
- {in: case1.0.in, out: case1.0.out, points: 0}
- {in: case1.1.in, out: case1.1.out, points: 100}
Normal cases
For normal cases, the test case will contain keys in and out, mapping to the path for the input and output files, respectively. The path is in the zip file if archive is defined, otherwise relative to the problem directory. A normal case will contain the key points.
Batched cases
The batch will contain the keys points and batched. batched will map to a list of batched cases, where each case contains in and out.
Optionally, the batch can contain a key dependencies. If specified, it should be a list of integers, indicating the one-indexed batch numbers that the batch depends on. This batch will only run if all of the dependent batches passed. The sample init.yml below is set up so the last batch only runs if the first two passed.
Note that if short circuit is enabled, it overrides this setting, i.e., a failed case implies immediate judging termination. Pretests (cases or batches with points: 0) also still imply immediate judging termination.
Below is a sample init.yml:
archive: tle16p4.zip
test_cases:
- {points: 0, in: tle16p4.p0.in, out: tle16p4.p0.out}
- {points: 10, in: tle16p4.p1.in, out: tle16p4.p1.out}
- points: 10
batched:
- {in: tle16p4.0.in, out: tle16p4.0.out}
- {in: tle16p4.1.in, out: tle16p4.1.out}
- points: 10
batched:
- {in: tle16p4.2.in, out: tle16p4.2.out}
- {in: tle16p4.3.in, out: tle16p4.3.out}
- points: 10
batched:
- {in: tle16p4.4.in, out: tle16p4.4.out}
- {in: tle16p4.5.in, out: tle16p4.5.out}
dependencies: [1, 2]
As FreateOJ's YAML dialect supports dynamic keys, large init.ymls can be programmatically generated. See the sample files for examples.
Specifying cases with regexes
If the test cases follow a similar format, it is possible to specify them with a regex.
The default regex for input files is ^(?=.*?\.in|in).*?(?:(?:^|\W)(?P<batch>\d+)[^\d\s]+)?(?P<case>\d+)[^\d\s]*$, and the default regex for output files is ^(?=.*?\.out|out).*?(?:(?:^|\W)(?P<batch>\d+)[^\d\s]+)?(?P<case>\d+)[^\d\s]*$.
Some examples of file formats they can match are:
test.1.in
test-1.in
test-case-1.in
test-1-2.in
test-batch-1-case-2.in
1.2.in
problem-1-case-1-batch-2.in
Where the first three are standalone cases (i.e. not in a batch) and the latter four are batched.
Note that non-batched cases treat their case number as their batch number, e.g.
1.in
2.1.in
2.2.in
3.in
would be sorted in this order by the judge.
These can be overwritten by specifying input_format and output_format within test_cases, respectively. The points awarded to each test case is given by case_points, and it defaults to 1 point per test case/batch. If there are many cases, a global points: can be used to set the points for all cases.
Problem examples
Custom checkers
A problem with many possible outputs (e.g. not a single possible answer, with score based on accuracy) may benefit from the checker field in the init.yml file. A checker is a Python script that is executed per-case post-execution — it grades the output of a process but does not interact with it.
The FreateOJ judge already defines several checkers. These checkers are specified as follows:
checker:
name: <name of checker>
args: {}
Where args is a dictionary of arguments to pass to the checker's check function. If you do not need to pass arguments, then you can write:
checker: <name of checker>
Standard checker - standard
If no checker field is specified, then the problem will default to the standard checker.
This checker returns True if the submission's output and the judge's output are equal, modulo whitespace. Specifically, the submission's output and the judge's output are split line-by-line and tokenized, with lines with no tokens being discarded. For each individual line, the submission's tokens must match exactly with the judge's tokens.
Easy checker - easy
This checker ignores all whitespace and letter case, then checks if the number of occurrences of each character are equal.
Floating point checkers - floats
The floats checker is used when outputs may suffer from floating point errors.
args can contain a key for precision, indicating an epsilon of 10-precision. This value defaults to 6.
Additionally, args can contain a key for error_mode. The supported values are:
default: check if the submission's output is within an absolute or relative error of epsilon.relative: only check for relative error.absolute: only check for absolute error.
Finally, all non-numeric outputs will be treated as strings, and will be compared for equality.
Absolute floating point error checker - floatsabs
floatsabs is an alias for floats with error_mode set to absolute.
Relative floating point error checker - floatsrel
floatsrel is an alias for floats with error_mode set to relative.
Identical checker - identical
The identical checker will check if the submission's output and the judge's output are identical, including whitespace.
args can contain a key for pe_allowed, which defaults to True. If pe_allowed is true, the checker will give the feedback Presentation Error, check your whitespace, if the output is correct modulo whitespace. Otherwise, the checker will return True if the two outputs are identical, and CheckerResult(False, 0, feedback=None) otherwise.
Line-by-line checker - linecount
The linecount checker is a custom checker primarily used for ECOO problems.
args can contain a key for feedback, which defaults to True. feedback indicates if the judge should give per-line feedback: a checkmark for a correct line, and an X for an incorrect line.
Sorted checker - sorted
The sorted checker checks if the submission's output and judge's output are equal, modulo their ordering.
args can contain a key for split_on, which defaults to lines. The supported values for split_on are:
lines: the checker will returnTrueif the two outputs are identical, modulo the ordering of their lines. Empty lines are ignored.whitespace: the checker will returnTrueif the two outputs are identical, modulo the ordering of their tokens.
Unordered checker - unordered
This is an alias for sorted with split_on equal to whitespace.
Custom checkers
A checker Python script must implement a function that is called by the judge:
def check(process_output, judge_output, **kwargs):
Variables in global scope will exist throughout the grading process.
**kwargs is a dictionary containing:
submission_source: the source code of the submission.judge_input: the judge's input.point_value: the point value of the test case.case_position: the index of the test case.batch: the batch the test case belongs to, or0if the test case is not in a batch.submission_language: the language the submission was submitted in.binary_data: a boolean, which isTrueif the data was not normalized to Linux line endings, andFalseotherwise.execution_time: the runtime of the program, in seconds.problem_id: the problem code.result: the submission's preliminaryResult.
Additionally, if the check method has the flag run_on_error set (i.e. check.run_on_error = True), it will be run on the submission's output, even if the submission received a preliminary IR/TLE/RTE/MLE verdict. ~~The only built-in checker that has this flag set is the linecount checker.~~ (Removed in this commit)
Returns
check can return either a CheckerResult object (from freateoj.result import CheckerResult), or a boolean (case_passed_bool). A CheckerResult can be instantiated through CheckerResult(case_passed_bool, points_awarded, feedback='').
Native checkers
Sometimes, problems may require a computationally expensive checker. In such cases, it is often beneficial to move the checker from the slow Python problem module and into a native language. This is handled by the bridged checker.
The bridged checker takes the following arguments:
files: either a filename, or a list of filenames, corresponding to the checker.lang: the language the checker is written in, using the same conventions as the judge.time_limit: the time limit allocated to the checker. It defaults toenv['generator_time_limit'].memory_limit: the memory limit allocated to the checker. It defaults toenv['generator_memory_limit'].compiler_time_limit: the time limit allocated to compiling the checker. It defaults toenv['generator_compiler_limit'].feedback: if true, the checker's standard output will be shown as feedback. Defaults to true.flags: compilation flags to pass to the checker.type: specifies the arguments to pass to the checker and how to interpret the checker's return code and output.- The
defaulttype passes the arguments in the orderinput_file output_file judge_file. A return code of0is an AC,1is a WA, and anything else results in an internal error. - The
testlibtype passes the arguments in the orderinput_file output_file judge_file. A return code of0is an AC,1is a WA,2is a presentation error,3corresponds to an assertion failing, and7, along with an output tostderrof the formatpoints Xfor an integer X awards X points. Anything else results in an internal error. - The
cocitype behaves similarly to thetestlibtype, but has partial formatpartial X/Y, which awards X/Y of the points. - The
pegtype exists for compatibility with the WCIPEG judge.
- The
The files will be compiled and sandboxed, then executed with the arguments input_file, output_file, and judge_file, which are files containing input, submission output, and judge output, respectively.
Custom graders
Custom grader behaviour
An init.yml object can contain a top-level custom_judge node, which contains a path to a Python file to be executed as a grader for the problem. The grader has access to the archive specified in archive.
In most use cases, either using one of the built-in graders, or a custom checker will suffice. A custom grader is only truly necessary if the normal interaction between the judge and the submission is insufficient.
class Grader(BaseGrader):
def grade(self, case):
pass
Parameters
case is a TestCase object.
case.positionis an integer, the current test case with a zero-based index.case.input_data()is a buffer containing the contents of theinfile specified for the current case ininit.yml. May beb'', if no case input file was specified.case.output_data()is a buffer containing the contents of theoutfile specified for the current case ininit.yml. May beb'', if no case output file was specified.case.pointsis an integer, the max points that can be awarded for the current test case.
Returns
A Result object (from freateoj.result import Result). Some notable fields include:
result_flag: stores a mask defining the current test case result code.proc_output: contains the string that will be displayed in the partial output pane. However, ifresult_flagisResult.AC, then the partial output pane will not be shown.feedback: contains the feedback given by the judge.extended_feedback: contains any extended feedback that would not fit into the shorterfeedbackfield, and is displayed as a separate pane beside the partial output on the site.
Example
To illustrate, in a problem where the process must echo a line of input, an interactive approach would look like this:
import subprocess
from freateoj.graders.standard import StandardGrader
from freateoj.result import Result
class Grader(StandardGrader):
def grade(self, case):
result = Result(case)
case_input = b'Hello, World!\n'
self._current_proc = self.binary.launch(
time=self.problem.time_limit,
memory=self.problem.memory_limit,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
wall_time=case.config.wall_time_factor * self.problem.time_limit,
)
output, error = self._current_proc.communicate(case_input)
self.binary.populate_result(error, result, self._current_proc)
if output == case_input:
result.extended_feedback = 'Correct answer! This will be displayed in the output pane.'
if result.result_flag == Result.AC:
result.points = case.points
else:
result.result_flag |= Result.WA
result.feedback = 'Wrong answer! :('
return result
A simple solution to this problem is print(input()).
The associated init.yml for this problem would look like this:
custom_judge: interactor.py
unbuffered: true
test_cases:
- points: 100
Since we use no input or output files (our test case is hardcoded), we do not need to specify the archive or related in and out fields.
In this example, it's important to note the unbuffered node. If set to true, the judge will use a pseudoterminal device for a submission's input and output pipes. Since ptys are not buffered by design, setting unbuffered to true removes the need for user submissions to flush() their output stream to guarantee that the grader receives their response. The unbuffered node is not exclusive to interactive grading: it may be specified regardless of judging mode.
Interactive grading
Interactive grading is used for problems where users should implement an online algorithm or where the grader must generate input or compute a score based on the user's previous output. Using an interactive grader is similar to using a custom grader: the custom_judge node also needs to be set. Rewriting the previous custom judge using an interactive grader would result in:
from freateoj.graders.interactive import InteractiveGrader
from freateoj.utils.unicode import utf8text
class Grader(InteractiveGrader):
def interact(self, case, interactor):
# The line to print
case_input = 'Hello, World!'
# Print the line, using the interactor
interactor.writeln(case_input)
# interact can return either a boolean, or a Result
return case_input == utf8text(interactor.readln())
Parameters
case is a TestCase object, and is identical to the one in the Grader section. interactor is an Interactor object.
interactor.read()reads all of the submission's output available.interactor.readln(strip_newline=True)reads the next line of the submission's output. Ifstrip_newlineis true, the trailing newline is stripped, otherwise it is retained.interactor.readtoken(delim=None)reads the next available token of the submission's output, as determined bystring.split(delim).interactor.readint(lo=float('-inf'), hi=float('inf'), delim=None)reads the next token of the submission's output, as determined bystring.split(delim). Additionally, the checker will automatically generate a wrong answer verdict if either the token cannot be converted to an integer, or if it is not in the range [lo, hi].interactor.readfloat(lo=float('-inf'), hi=float('inf'), delim=None)reads the next token of the submission's output, as determined bystring.split(delim). Additionally, the checker will automatically generate a wrong answer verdict if either the token cannot be converted to a float, or if it is not in the range [lo, hi].interactor.write(val)writesval, cast to a string, to the submission's standard input.interactor.writeln(val)writesval, cast to a string, to the submission's standard input, followed by a newline.interactor.close()closes the submission'sstdinstream.
Returns
Either a boolean or a Result (from freateoj.result import Result) object. The boolean is True if the submission should score full points, and False otherwise. The Result object is handled the same way as custom graders.
Native interactive grading
Sometimes, an interactive grader will be very computationally expensive. In these cases, one can use the bridged grader. To invoke the bridged grader, interactive should be a top-level node that contains files. files is either a single filename, or a list of filenames, corresponding to the interactor.
Optional arguments are:
lang: the language of the interactor. If empty, the judge will attempt to detect the language from the filename extension(s). Currently, the judge can detect.cpp,.cc, and.c.flags: flags to pass to the compiler.compiler_time_limit: the time limit allocated to compiling the interactor. It defaults toenv['compiler_time_limit'].preprocessing_time: the interactor's time limit is equal to this value plus the time limit of the problem, in seconds. It defaults to 2.memory_limit: the memory limit allocated to the interactor. It defaults toenv['generator_memory_limit'].type: specifies the arguments to pass to the checker and how to interpret the checker's return code and output.- The
defaulttype passes the arguments in the orderinput_file judge_file. A return code of0is an AC,1is a WA, and anything else results in an internal error. - The
testlibtype passes the arguments in the orderinput_file output_file judge_file. Note thatoutput_filewill always be/dev/null, and is passed to maintain compatibility withtestlib.h. A return code of0is an AC,1is a WA,2is a presentation error,3corresponds to an assertion failing, and7, along with an output tostderrof the formatpoints Xfor an integer X awards X points. Anything else results in an internal error. - The
cocitype passes the arguments in the orderinput_file judge_file. Its parsing of return codes is the same as thetestlibtype, but has partial formatpartial X/Y, which awards X/Y of the points. - The
pegtype exists for compatibility with the WCIPEG judge, and is not meant to be used here.
- The
The interactor's standard input is connected to the submission's standard output, and vice versa. After the interactor prints, it is required to flush.
To specify a correct answer, the interactor should return 0. To specify an incorrect answer, the interactor should return 1. All other return values will be considered internal errors. To override this behaviour, you can change type to a valid contrib module, such as testlib.
Example
An example init.yml would be as follows:
unbuffered: true
archive: seed2.zip
interactive: {files: interactor.cpp, type: testlib}
test_cases:
- {in: seed2.1.in, points: 20}
- {in: seed2.2.in, points: 20}
- {in: seed2.3.in, points: 20}
- {in: seed2.4.in, points: 20}
- {in: seed2.5.in, points: 20}
An example of interactor is as follows. Note that it is not necessary to flush, even if unbuffered is false.
#include <cstdio>
#include <cstdlib>
inline void read(long long *i) {
if (scanf("%lld", i) != 1 || *i < 1 || *i > 2000000000)
exit(2);
}
int main(int argc, char *argv[]) {
FILE *input_file = fopen(argv[1], "r");
int N, guesses = 0;
long long guess;
fscanf(input_file, "%d", &N);
while (guess != N) {
read(&guess);
if (guess == N) {
puts("OK");
} else if (guess > N) {
puts("FLOATS");
} else {
puts("SINKS");
}
guesses++;
}
if (guesses <= 31)
return 0; // AC
else
return 1; // WA
}
Function signature grading (IOI-style)
Signature grading is used for problems where users should implement an online algorithm or interact with the grader directly without the need for traditional input and output routines. This is commonly seen in competitions such as the IOI, where all input is passed through function arguments and output is replaced with return values or directly modifying specifically allocated memory for the computed answer.
The following languages are supported for this mode:
- The C family: C, C11, Clang
- The C++ family: C++03, C++11, C++14, C++17, C++20, Clang++
signature_grader should be a top-level node that contains entry and header. entry is a C or C++ file that contains the main function. It should read input from stdin, call the user's implemented functions specified in header, and write output to stdout. You may specify a custom checker to interpret the entry's output. If no custom checker is specified, it will be compared to the output file using the default checker.
The user's submission will be automatically modified to include the file header, and the symbol main is redefined as main_GUID where GUID is a randomly generated GUID. This is so users testing their program do not have to manually remove their main function before submissions; it does not protect against the preprocessor directive #undef main.
The global variables in the entry should be declared static to prevent name collisions. Optimally, header should have an include guard, in case it contains something other than function prototypes.
Example
An example of the init.yml:
signature_grader: {entry: handler.c, header: header.h}
test_cases:
- {in: siggrade.1.in, out: siggrade.1.out, points: 50}
- {in: siggrade.2.in, out: siggrade.2.out, points: 50}
An example of the entry file:
#include "header.h"
#include <stdbool.h>
#include <stdio.h>
static int n;
int main() {
scanf("%d", &n);
bool valid = is_valid(n); // Defined in header
printf(valid ? "correct" : "wrong");
return 0;
}
An example of the header file:
#ifndef _GRADER_HEADER_INCLUDED
#define _GRADER_HEADER_INCLUDED
#include <stdbool.h>
bool is_valid(int);
#endif
Generators
When there is a large amount of test data, a generator file can be used instead of input and output files. A generator is a program that takes command line arguments for each case, and outputs the input and output data for each case.
The generator node
The generator node can contain either:
- a single value, the name of the generator file.
- an array, in which case the first element is the source file (in either C or C++), and the remaining elements are auxiliary files, such as header files.
- a YAML associative array that can contain the following keys:
source: either a single string: the name of generator file, or an array, in which case the first file is the generator source, and the remaining files are auxiliary files (e.g. header files).language: the language the generator is written in. If empty, the judge tries to infer the language fromsource.flags: additional flags to pass to the compiler. It defaults to[].compiler_time_limit: the compiler time limit for the generator. It defaults toenv.compiler_time_limit, as defined infreateoj/judgeenv.py. It is recommended to set this value to 60 seconds if usingtestlib.h.time_limit: the time limit allocated to the generator. It defaults toenv.time_limit, as defined infreateoj/judgeenv.py.memory_limit: the memory limit allocated to the generator. It defaults toenv.memory_limit, as defined infreateoj/judgeenv.py.
Additionally, it is possible to specify this node in each test case, so several generators can be used for a single problem.
Generator arguments
The generator_args node contains a list of arguments that will be cast to a Python str, then passed to the compiled generator. generator_args can be specified as a top-level node, or more commonly, as a key in a test case node. For example, consider:
generator: gen.cpp
test_cases:
- {generator_args: [false, 123, "a b\nc"], points: 10}
- {points: 20}
For the first test case, the generator will receive 4 arguments: "_aux_file", "False", "123", "a b\nc". For the second test case, generator_args defaults to [], so the generator will receive 1 argument: "_aux_file".
The generator should output the test case's input data to stdout, and the output data to stderr.
If a test case already has an input file and output file defined by the in and out keys, the generator will not be run for that test case.
Math Syntax Guide
FreateOJ uses MathJax 3.2.0 for rendering mathematical expressions. This guide covers the syntax conventions used across the platform.
Quick Reference
| Type | Syntax | Example | Renders |
|---|---|---|---|
| Inline | $expr$ | $x^2$ | x² |
| Display | $$expr$$ | $$\sum_{i=1}^{n} i$$ | ∑i=1n i |
Inline Math
Use single dollar signs $ to wrap inline mathematical expressions:
The value of $\pi$ is approximately 3.14159.
Renders as: The value of pi is approximately 3.14159.
Common Examples
| Expression | Syntax |
|---|---|
| Superscript | $x^2$ |
| Subscript | $a_i$ |
| Fraction | $\frac{a}{b}$ |
| Square root | $\sqrt{n}$ |
| Summation | $\sum_{i=1}^{n} a_i$ |
| Integral | $\int_0^1 f(x) \, dx$ |
| Limit | $\lim_{n \to \infty} a_n$ |
| Binomial | $\binom{n}{k}$ |
Display Math
Use double dollar signs $$ for centered, display-style equations:
$$\sum_{i=1}^{n} i = \frac{n(n+1)}{2}$$
Piecewise Functions
$$f(x) = \begin{cases} 1 & \text{if } x \ge 0 \\ -1 & \text{if } x < 0 \end{cases}$$
Common LaTeX Commands
Greek Letters
| Letter | Syntax | Letter | Syntax |
|---|---|---|---|
| α | $\alpha$ | Α | $\Alpha$ |
| β | $\beta$ | Β | $\Beta$ |
| γ | $\gamma$ | Γ | $\Gamma$ |
| δ | $\delta$ | Δ | $\Delta$ |
| ε | $\epsilon$ | Ε | $\Epsilon$ |
| θ | $\theta$ | Θ | $\Theta$ |
| λ | $\lambda$ | Λ | $\Lambda$ |
| μ | $\mu$ | Μ | $\Mu$ |
| π | $\pi$ | Π | $\Pi$ |
| σ | $\sigma$ | Σ | $\Sigma$ |
| φ | $\phi$ | Φ | $\Phi$ |
| ω | $\omega$ | Ω | $\Omega$ |
Operators
| Operation | Syntax |
|---|---|
| Modulo | $a \bmod b$ |
| Logarithm | $\log n$ |
| Natural log | $\ln n$ |
| Greatest common divisor | $\gcd(a, b)$ |
| Maximum | $\max(a, b)$ |
| Minimum | $\min(a, b)$ |
| Absolute value | $\|x\|$ |
| Floor | $\lfloor x \rfloor$ |
| Ceiling | $\lceil x \rceil$ |
Sets and Logic
| Symbol | Syntax |
|---|---|
| Set | $\{1, 2, 3\}$ |
| Union | $A \cup B$ |
| Intersection | $A \cap B$ |
| Subset | $A \subseteq B$ |
| Element of | $x \in A$ |
| For all | $\forall$ |
| Exists | $\exists$ |
| Therefore | $\therefore$ |
| Because | $\because$ |
Big Operators
| Symbol | Syntax |
|---|---|
| Sum | $\sum_{i=1}^{n} a_i$ |
| Product | $\prod_{i=1}^{n} a_i$ |
| Union | $\bigcup_{i=1}^{n} A_i$ |
| Intersection | $\bigcap_{i=1}^{n} A_i$ |
Complexity Notation
The time complexity is $\mathcal{O}(N \log N)$.
Renders as: The time complexity is O(N log N).
Colored Math
FreateOJ supports the \color command:
$\color{red}x + \color{blue}y$
Tips
- Escape dollar signs: Use
\$for a literal dollar sign outside math mode - Spacing: Use
\,,\;,\:for different spacing widths - Newlines in display math: Use
\\for line breaks - Text in math: Use
\text{...}for text within expressions - Operators: Use
\operatorname{...}for custom operators
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright © 2007 Free Software Foundation, Inc. https://fsf.org/
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based on the Program.
To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work.
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
- a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
- b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices".
- c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
- a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
- b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
- d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
- e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements.
You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see https://www.gnu.org/licenses/.