CamCOPS
CamCOPS – Server
Back to Documentation
www.camcops.org/documentation/server.html

Your CamCOPS server

Configuring the server

Overview

Block diagram of CamCOPS server

Configure your firewall

Ensure your firewall is configured properly:

Install CamCOPS and its dependencies

Summary of relevant Linux distribution differences

Ubuntu CentOS 6.5
System
Heritage Debian Red Hat Enterprise Linux
Package management apt-get, dpkg, and for convenience gdebi yum
General system update sudo apt-get update && sudo apt-get dist-upgrade sudo yum update
Package type deb rpm
Extra security Nothing complex by default SELinux; file permissions must be set with chcon
Autostarting daemons at boot Usually happens automatically Usually need to configure with chkconfig
Python
Default Python versions (CamCOPS requires 3.4) 2.7, 3.4 2.6
Apache
Apache main configuration file Usually /etc/apache2/apache2.conf Usually /etc/httpd/conf/httpd.conf
Apache SSL configuration file Usually /etc/apache2/sites-available/default-ssl Usually /etc/httpd/conf.d/ssl.conf
Granting access in Apache config file Apache 2.4:
Require all granted
Apache 2.2:
Order allow,deny
Allow from all
Default Apache system user www-data apache
Default SSL certificate location /etc/ssl/ /etc/pki/tls/
Default Apache log directory /var/log/apache2/ /var/log/httpd/
Restarting Apache sudo service apache2 [start|stop|restart]
sudo apache2ctl [start|stop|restart]
sudo apachectl [start|stop|restart]
sudo service httpd [start|stop|restart]
sudo apachectl [start|stop|restart]
supervisord
supervisord configuration file Usually /etc/supervisor/supervisord.conf Usually /etc/supervisord.conf
Restarting supervisord sudo service supervisor [start|stop|restart]
sudo supervisorctl
sudo service supervisord [start|stop|restart]
sudo supervisorctl
MySQL
Default MySQL configuration file /etc/mysql/my.cnf /etc/my.cnf
Restarting MySQL sudo service mysql [start|stop|restart] sudo service mysqld [start|stop|restart]
Default MySQL log /var/log/mysql.log /var/log/mysqld.log
CamCOPS packages
Installation sudo gdebi install camcops_VERSION_all.deb sudo yum install camcops_VERSION.noarch.rpm
Removal sudo dpkg --remove camcops sudo yum remove camcops

Ubuntu 12.04 (a Debian-based Linux)

  1. Install Apache and MySQL. (As part of the MySQL installation, you will create a root database user. Remember the password!)
    sudo apt-get install apache2 mysql-client mysql-server
  2. Download the Debian package, which will be called camcops_VERSION_all.deb, where VERSION is the current version number.
  3. Install the packages and its dependencies:
    sudo gdebi camcops_VERSION_all.deb
    
    (If you don’t have gdebi, install it with sudo apt-get install gdebi.) CamCOPS will now be installed in /usr/share/camcops.

CentOS 6.5 (an RPM-based Linux)

CamCOPS is designed for Debian Linux, so the installation procedure is much simpler there. Frankly, achieving this on CentOS 6.5 is fiddly; try to get a Ubuntu server. Nevertheless, it can be done:

  1. Ensure additional repositories are in use:
    # RPMForge: see http://www.tecmint.com/install-and-enable-rpmforge-repository-in-rhel-centos-6-5-4/
    # Use "cat /etc/centos-release" to see CentOS version; use "uname -a" to detect 32-bit/64-bit version.
    # For example, for 64-bit CentOS 6.5:
    
    sudo rpm -Uvh http://packages.sw.be/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm
    
    # EPEL: see http://fedoraproject.org/wiki/EPEL
    # For example:
    
    sudo rpm -Uvh https://anorien.csc.warwick.ac.uk/mirrors/epel/6/i386/epel-release-6-8.noarch.rpm
    
    # Something providing Python 3 in package form (see http://stackoverflow.com/questions/8087184):
    sudo yum install https://centos6.iuscommunity.org/ius-release.rpm
    
    # We need MySQL 5.5: http://www.webtatic.com/packages/mysql55/
    sudo rpm -Uvh http://mirror.webtatic.com/yum/el6/latest.rpm
    
  2. Potential prerequisites for what follows:
    sudo yum install blas-devel lapack-devel libffi-devel libpng-devel links nano openssl-devel python-devel
  3. Install Python 3.4 (which comes with pip and setuptools). Note: CentOS 6.5 (December 2013) provides Python 2.6 (2009). You can’t just replace it, because its system scripts need Python 2.6. CentOS is based on Red Hat Enterprise Linux. Fedora 14 (another Red Hat derivative) moved to Python 2.7 in 2010. CamCOPS needs Python 3 (e.g. 3.4).
    sudo yum install python34u
    
    # Test:
    python3 --version
    pip3 --version
    
  4. Install MySQL:
    sudo yum install mysql55 mysql55-server mysql-devel
  5. Install Apache:
    sudo yum install httpd httpd-devel mod_ssl
  6. Download the CamCOPS RPM package. This will be called camcops-VERSION.noarch.rpm, where VERSION is the current version number.
  7. Install the package (which will automatically install its dependencies):
    sudo yum install camcops_VERSION.noarch.rpm
    
    # Or, for more verbosity and to say yes to everything, use this command instead:
    # sudo yum --assumeyes --verbose --rpmverbosity=debug install camcops_VERSION.noarch.rpm
    # ... but, curiously, yum temporarily swallows the output from the post-install
    #     scripts and only spits it out at the end. This makes it look like the
    #     installation has got stuck (because packages like numpy are very slow
    #     to install); use watch pstree or top to reassure yourself
    #     that progress is indeed happening.
    
    Recognizing success: if you type camcops, see an announcement of the CamCOPS version, and get asked for a configuration filename, CamCOPS is running happily from the command line.

Set up MySQL and create a database ready for CamCOPS to use

  1. Under Ubuntu, if you are happy to leave the data files at their default location, skip this step. Check/edit the MySQL configuration (see table above for filenames). See Getting Started with MySQL. In particular:
    • datadir should point to your database files (default often /var/lib/mysql, but /srv/mysql is one alternative).
    • Other options are explained here.
    • If you create a blank directory (such as /srv/mysql), you will need to use the mysql_install_db tool; see Postinstallation Setup and Testing; an example is sudo mysql_install_db --user=mysql --basedir=/usr --datadir=/srv/mysql
    • Manual start: sudo /usr/bin/mysqld_safe --user=mysql &. Manual stop: sudo mysqladmin shutdown.
    • Service start/stop: see table above.
    • If it starts manually but not as a service (in a manner that depends on your data directory), you have a challenging problem; an option is to return to the default data directory!
    • To log in prior to securing the database: mysql.
    • CentOS MySQL installation guide.
    • Default logfile: /var/log/mysqld.log or /var/log/mysql/...
  2. Secure your MySQL installation by running mysql_secure_installation.
    • Login after securing: mysql -u root -p.
    • Similar username/password requirements now apply to manual shutdowns.
  3. Ensure that the max_allowed_packet parameter is large enough.
    • This parameter needs to be set large enough that the largest binary large objects (BLOBs) can be uploaded. CamCOPS BLOBs are mostly photographs from tablets. A high-end tablet in 2014 might have an 8 megapixel (MP) camera, with each pixel taking 3 bytes, i.e. 24 Mb. Furthermore, the transfer takes more space thanks to somewhat inefficient encoding. The MySQL server default value is just 1 Mb.
    • You must set this parameter for the server, and for the mysqldump tool.
    • A suggested value is 32 Mb. Edit my.cnf to include max_allowed_packet values the [mysqld] and [mysqldump] sections (creating them if necessary).
    • Similar editing of the [client] section of my.cnf is unnecessary, firstly because some other MySQL clients may not recognize the option and might choke on it, and secondly because CamCOPS uses MySQLdb (MySQL-Python), which uses the MySQL C API, which has a default limit of 1 Gb.
  4. Set some other MySQL parameters for TEXT-heavy tables; see FAQs/bugs.
  5. Thus, edit my.cnf to include the following:
    [mysqld]
    max_allowed_packet = 32M
    
    innodb_strict_mode = 1
    innodb_file_per_table = 1
    innodb_file_format = Barracuda
    
    # Only for MySQL prior to 5.7.5 (http://dev.mysql.com/doc/relnotes/mysql/5.6/en/news-5-6-20.html):
    # innodb_log_file_size = 512M
    
    [mysqldump]
    max_allowed_packet = 32M
  6. Ensure MySQL is running as a service (as above).
  7. Create the CamCOPS database. Log in to MySQL using the root user details you created earlier, and follow these commands (which you’ll find at /usr/share/camcops/demo_mysql_database_creation):
    # First, from the Linux command line, log in to MySQL as root:
    
    mysql --host=127.0.0.1 --port=3306 --user=root --password
    # ... or the usual short form: mysql -u root -p
    
    # Create the database:
    
    CREATE DATABASE camcops;
    
    # Ideally, create another user that only has access to the CamCOPS database.
    # You should do this, so that you don’t use the root account unnecessarily.
    
    GRANT ALL PRIVILEGES ON camcops.* TO 'YYYYYY_REPLACE_ME'@'localhost' IDENTIFIED BY 'ZZZZZZ_REPLACE_ME';
    
    # For future use: if you plan to explore your database directly for analysis,
    # you may want to create a read-only user. Though it may not be ideal (check:
    # are you happy the user can see the audit trail?), you can create a user with
    # read-only access to the entire database like this:
    
    GRANT SELECT camcops.* TO 'QQQQQQ_REPLACE_ME'@'localhost' IDENTIFIED BY 'PPPPPP_REPLACE_ME';
    
    # All done. Quit MySQL:
    
    exit
        
    
  8. Ensure MySQL restarts on boot. On Ubuntu, this should be automatic. On CentOS, run sudo chkconfig --level 2345 mysqld on. To check, run sudo chkconfig --list | grep mysqld

Configure CamCOPS

Edit (or copy and edit) /etc/camcops/camcops.conf, specifying (at the least) your database username/password and your local institution’s URL. There are a sequence of parameter = value lines, mostly under the section heading [server]. A sample configuration file is:

# =============================================================================
# Format of the CamCOPS configuration file
# =============================================================================
# COMMENTS. Hashes (#) and semicolons (;) mark out comments.
# SECTIONS. Sections are indicated with: [section]
# NAME/VALUE (KEY/VALUE) PAIRS.
# - The parser used is ConfigParser (Python).
# - It allows "name=value" or "name:value".
# - BOOLEAN OPTIONS. For Boolean options, TRUE values are any of: 1, yes, true,
#   on (case-insensitive). FALSE values are any of: 0, no, false, off.
# - UTF-8 encoding. Use this. For ConfigParser, the file is explicitly opened
#   in UTF-8 mode.
# - Avoid indentation.

# =============================================================================
# Main section: [server]
# =============================================================================

[server]

# -----------------------------------------------------------------------------
# Database connection/tools
# -----------------------------------------------------------------------------

# DB_NAME: MySQL database name.
# If you didn't call the database 'DEFAULT_DB_NAME}', edit the next line:

DB_NAME = camcops

# DB_USER: MySQL database username.
# DB_PASSWORD: MySQL database password.
# Edit the next two lines:

DB_USER = YYYYYY_REPLACE_ME
DB_PASSWORD = ZZZZZZ_REPLACE_ME

# DB_SERVER: MySQL database server (default: localhost).
# DB_PORT: MySQL database port (default: 3306).
# These values are unlikely to need modification:

DB_SERVER = localhost
DB_PORT = 3306

# MYSQL: Specify the full path to the mysql executable, by default
# /usr/bin/mysql (used for data dumps for privileged users).

MYSQL = /usr/bin/mysql

# MYSQLDUMP: Specify the full path to the mysqldump executable, by default
# /usr/bin/mysqldump (used for data dumps for privileged users).

MYSQLDUMP = /usr/bin/mysqldump

# -----------------------------------------------------------------------------
# Database title and ID descriptions
# -----------------------------------------------------------------------------
# NOTE: WHENEVER YOU CHANGE THESE, YOU MUST USE THE CAMCOPS COMMAND-LINE TOOL
# TO REWRITE THEM INTO THE DATABASE.

# DATABASE_TITLE: the friendly title of your database (as Unicode UTF-8).

DATABASE_TITLE = My First CamCOPS Database

# IDDESC_1 to IDDESC_8: full descriptions of each of the possible ID numbers.
# IDSHORTDESC_1 to IDSHORTDESC_8: short versions of the same descriptions.
# All are Unicode UTF-8.

IDDESC_1 = NHS number
IDSHORTDESC_1 = NHS
IDDESC_2 = CPFT RiO number
IDSHORTDESC_2 = RiO
IDDESC_3 = CPFT M number
IDSHORTDESC_3 = M
IDDESC_4 = Addenbrooke’s number
IDSHORTDESC_4 = Add
IDDESC_5 = Papworth number
IDSHORTDESC_5 = Pap
IDDESC_6 = Hinchingbrooke number
IDSHORTDESC_6 = Hinch
IDDESC_7 = Peterborough City Hosp number
IDSHORTDESC_7 = PCH
IDDESC_8 = Spare_8_idnum
IDSHORTDESC_8 = Spare8

# -----------------------------------------------------------------------------
# ID policies
# -----------------------------------------------------------------------------
# NOTE: WHENEVER YOU CHANGE THESE, YOU MUST USE THE CAMCOPS COMMAND-LINE TOOL
# TO REWRITE THEM INTO THE DATABASE.

# UPLOAD_POLICY and FINALIZE_POLICY define two ID policies. The upload policy
# is the looser policy, allowing upload from tablets to the server. The
# finalize policy is typically the same or stricter, and allows records to
# be deleted from the tablets. See documentation.
#
# Case-insensitive. Valid tokens are:
#   (
#   )
#   AND
#   OR
#   forename
#   surname
#   dob
#   sex
#   idnum1 ... idnum8
#
# Liaison psychiatry upload example, allowing upload with any of multiple
# institutional IDs, but finalizing only with the institution's core ID:
#
# UPLOAD_POLICY = forename AND surname AND dob AND sex AND (idnum1 OR idnum2 OR idnum3 OR idnum4 OR idnum5 OR idnum6 OR idnum7 OR idnum8)
# FINALIZE_POLICY = forename AND surname AND dob AND sex AND idnum1
#
# Research pseudonym example, in which a single number is used and no
# patient-identifying information:
#
# UPLOAD_POLICY = sex AND idnum1
# FINALIZE_POLICY = sex AND idnum1

UPLOAD_POLICY = forename AND surname AND dob AND sex AND (idnum1 OR idnum2 OR idnum3 OR idnum4 OR idnum5 OR idnum6 OR idnum7 OR idnum8)
FINALIZE_POLICY = forename AND surname AND dob AND sex AND idnum1

# -----------------------------------------------------------------------------
# URLs and paths
# -----------------------------------------------------------------------------

# =============================================================================
# Site URL configuration
# =============================================================================

# A quick note on absolute and relative URLs, and how CamCOPS is mounted.
#
# Suppose your CamCOPS site is visible at
#       https://www.somewhere.ac.uk/camcops_smith_lab/webview
#       ^      ^^                 ^^                ^^      ^
#       +------++-----------------++----------------++------+
#       |       |                  |                 |
#       1       2                  3                 4
#
# Part 1 is the protocol, and part 2 the machine name.
# Part 3 is the mount point. The main server (e.g. Apache) knows where the
# CamCOPS script is mounted (in this case /camcops_smith_lab). It does NOT
# tell the script via the script's WSGI environment. Therefore, if the script
# sends HTML including links, the script can operate only in relative mode.
# For it to operate in absolute mode, it would need to know (3).
# Part 4 is visible to the CamCOPS script.
#
# If CamCOPS used URLs starting with '/', it would need to be told at least
# part (3). To use absolute URLs, it would need to know all of (1), (2), (3).
# We will follow others (e.g. http://stackoverflow.com/questions/2005079) and
# use only relative URLs.

# LOCAL_INSTITUTION_URL: Clicking on your institution's logo in the CamCOPS
# menu will take you to this URL.
# Edit the next line to point to your institution:

LOCAL_INSTITUTION_URL = http://www.mydomain/

# LOCAL_LOGO_FILE_ABSOLUTE: Specify the full path to your institution's logo
# file, e.g. /var/www/logo_local_myinstitution.png . It's used for PDF
# generation; HTML views use the fixed string "static/logo_local.png", aliased
# to your file via the Apache configuration file).
# Edit the next line to point to your local institution's logo file:

LOCAL_LOGO_FILE_ABSOLUTE = /usr/share/camcops/server/static/logo_local.png

# CAMCOPS_LOGO_FILE_ABSOLUTE: similarly, but for the CamCOPS logo.
# It's fine not to specify this.

# CAMCOPS_LOGO_FILE_ABSOLUTE = /usr/share/camcops/server/static/logo_camcops.png

# MAIN_STRING_FILE: Main strings.xml file to be used by the server.
# file and other resources (set by the installation script;
# default /usr/share/camcops/tablet/i18n/en/strings.xml).

MAIN_STRING_FILE = /usr/share/camcops/tablet/i18n/en/strings.xml

# EXTRA_STRING_FILES: multiline list of filenames (with absolute paths), read
# by the server, and used as EXTRA STRING FILES (in addition to the main
# strings.xml file specified by MAIN_STRING_FILE above). Optional.
# However, if you specify it, don't remove the default of
#   /usr/share/camcops/server/extra_strings
# or you will lose some functionality of some core tasks.
# May use "glob" pattern-matching (see
# https://docs.python.org/3.5/library/glob.html).

EXTRA_STRING_FILES = /usr/share/camcops/server/extra_strings/*

# INTROSPECTION: permits the offering of CamCOPS source code files to the user,
# allowing inspection of tasks' internal calculating algorithms. Default is
# true.

INTROSPECTION = true

# HL7_LOCKFILE: filename stem used for process locking for HL7 message
# transmission. Default is /var/lock/camcops/camcops.hl7
# The actual lockfile will, in this case, be called
#     /var/lock/camcops/camcops.hl7.lock
# and other process-specific files will be created in the same directory (so
# the CamCOPS script must have permission from the operating system to do so).
# The installation script will create the directory /var/lock/camcops

HL7_LOCKFILE = /var/lock/camcops/camcops.hl7

# SUMMARY_TABLES_LOCKFILE: file stem used for process locking for summary table
# generation. Default is /var/lock/camcops/camcops.summarytables.
# The lockfile will, in this case, be called
#     /var/lock/camcops/camcops.summarytables.lock
# and other process-specific files will be created in the same directory (so
# the CamCOPS script must have permission from the operating system to do so).
# The installation script will create the directory /var/lock/camcops

SUMMARY_TABLES_LOCKFILE = /var/lock/camcops/camcops.summarytables

# WKHTMLTOPDF_FILENAME: for the pdfkit PDF engine, specify a filename for
# wkhtmltopdf that incorporates any need for an X Server (not the default
# /usr/bin/wkhtmltopdf). See http://stackoverflow.com/questions/9604625/ .
# A suitable one is bundled with CamCOPS, so you shouldn't have to alter this
# default. Default is None, which usually ends up calling /usr/bin/wkhtmltopdf

WKHTMLTOPDF_FILENAME =

# -----------------------------------------------------------------------------
# Login and session configuration
# -----------------------------------------------------------------------------

# SESSION_TIMEOUT_MINUTES: Time after which a session will expire (default 30).

SESSION_TIMEOUT_MINUTES = 30

# PASSWORD_CHANGE_FREQUENCY_DAYS: Force password changes (at webview login)
# with this frequency (0 for never). Note that password expiry will not prevent
# uploads from tablets, but when the user next logs on, a password change will
# be forced before they can do anything else.

PASSWORD_CHANGE_FREQUENCY_DAYS = 0

# LOCKOUT_THRESHOLD: Lock user accounts after every n login failures (default
# 10).

LOCKOUT_THRESHOLD = 10

# LOCKOUT_DURATION_INCREMENT_MINUTES: Account lockout time increment (default
# 10).
# Suppose LOCKOUT_THRESHOLD = 10 and LOCKOUT_DURATION_INCREMENT_MINUTES = 20.
# After the first 10 failures, the account will be locked for 20 minutes.
# After the next 10 failures, the account will be locked for 40 minutes.
# After the next 10 failures, the account will be locked for 60 minutes, and so
# on. Time and administrators can unlock accounts.

LOCKOUT_DURATION_INCREMENT_MINUTES = 10

# DISABLE_PASSWORD_AUTOCOMPLETE: if true, asks browsers not to autocomplete the
# password field on the main login page. The correct setting for maximum
# security is debated (don't cache passwords, versus allow a password manager
# so that users can use better/unique passwords). Default: true.
# Note that some browsers (e.g. Chrome v34 and up) may ignore this.

DISABLE_PASSWORD_AUTOCOMPLETE = true

# -----------------------------------------------------------------------------
# Suggested filenames for saving PDFs from the web view
# -----------------------------------------------------------------------------
# Try with Chrome, Firefox. Internet Explorer may be less obliging.

# PATIENT_SPEC_IF_ANONYMOUS: for anonymous tasks, this string is used as the
# patient descriptor (see also PATIENT_SPEC, SUGGESTED_PDF_FILENAME below).
# Typically "anonymous".

PATIENT_SPEC_IF_ANONYMOUS = anonymous

# PATIENT_SPEC: string, into which substitutions will be made, that defines the
# {patient} element available for substitution into the *_FILENAME_SPEC
# variables (see below). Possible substitutions:
#
#   {surname}      : patient's surname in upper case
#   {forename}     : patient's forename in upper case
#   {dob}          : patient's date of birth (format "%Y-%m-%d", e.g.
#                    2013-07-24)
#   {sex}          : patient's sex (M, F, X)
#
#   {idshortdesc1} : short description of the relevant ID number, if that ID
#   ...              number is not blank; otherwise blank
#   {idshortdesc8}
#
#   {idnum1}       : ID numbers
#   ...
#   {idnum8}
#
#   {allidnums}    : all available ID numbers in "shortdesc-value" pairs joined
#                    by "_". For example, if ID numbers 1, 4, and 5 are
#                    non-blank, this would have the format
#                    idshortdesc1-idval1_idshortdesc4-idval4_idshortdesc5-idval5

PATIENT_SPEC = {surname}_{forename}_{allidnums}

# TASK_FILENAME_SPEC:
# TRACKER_FILENAME_SPEC:
# CTV_FILENAME_SPEC:
# Strings used for suggested filenames to save from the webview, for tasks,
# trackers, and clinical text views. Substitutions will be made to determine
# the filename to be used for each file. Possible substitutions:
#
#   {patient}   : Patient string. If the task is anonymous, this is
#                 PATIENT_SPEC_IF_ANONYMOUS; otherwise, it is defined by
#                 PATIENT_SPEC above.
#   {created}   : Date/time of task creation.  Dates/times are of the format
#                 "%Y-%m-%dT%H%M", e.g. 2013-07-24T2004. They are expressed in
#                 the timezone of creation (but without the timezone
#                 information for filename brevity).
#   {now}       : Time of access/download (i.e. time now), in local timezone.
#   {tasktype}  : Base table name of the task (e.g. "phq9"). May contain an
#                 underscore. Blank for to trackers/CTVs.
#   {serverpk}  : Server's primary key. (In combination with tasktype, this
#                 uniquely identifies not just a task but a version of that
#                 task.) Blank for trackers/CTVs.
#   {filetype}  : e.g. "pdf", "html", "xml" (lower case)
#   {anonymous} : evaluates to PATIENT_SPEC_IF_ANONYMOUS if anonymous,
#                 otherwise ""
#   ... plus all those substitutions applicable to PATIENT_SPEC
#
# After these substitutions have been made, the entire filename is then
# processed to ensure that only characters generally acceptable to filenames
# are used (see convert_string_for_filename() in the CamCOPS source code).
# Specifically:
#
#   - Unicode converted to 7-bit ASCII (will mangle, e.g. removing accents)
#   - spaces converted to underscores
#   - characters are removed unless they are one of the following: all
#     alphanumeric characters (0-9, A-Z, a-z); '-'; '_'; '.'; and the
#     operating-system-specific directory separator (Python's os.sep, a forward
#     slash '/' on UNIX or a backslash '' under Windows).

TASK_FILENAME_SPEC = CamCOPS_{patient}_{created}_{tasktype}-{serverpk}.{filetype}
TRACKER_FILENAME_SPEC = CamCOPS_{patient}_{now}_tracker.{filetype}
CTV_FILENAME_SPEC = CamCOPS_{patient}_{now}_clinicaltextview.{filetype}

# -----------------------------------------------------------------------------
# Debugging options
# -----------------------------------------------------------------------------
# Possible log levels are (case-insensitive): "debug", "info", "warn"
# (equivalent: "warning"), "error", and "critical" (equivalent: "fatal").

# WEBVIEW_LOGLEVEL: Set the level of detail provided from the webview to the
# Apache server log. (Loglevel option; see above.)

WEBVIEW_LOGLEVEL = info

# DBENGINE_LOGLEVEL: Set the level of detail provided from the underlying
# database transaction handler to the Apache server log. More of a security
# risk than WEBVIEW_DEBUG_OUTPUT. (Loglevel option; see above.)

DBENGINE_LOGLEVEL = info

# DBCLIENT_LOGLEVEL: Set the log level for the tablet client database access
# script. (Loglevel option; see above.)

DBCLIENT_LOGLEVEL = info

# ALLOW_INSECURE_COOKIES: DANGEROUS option that removes the requirement that
# cookies be HTTPS (SSL) only.

ALLOW_INSECURE_COOKIES = false

# -----------------------------------------------------------------------------
# Analytics
# -----------------------------------------------------------------------------

# SEND_ANALYTICS: Send analytics to the CamCOPS base in Cambridge? (Boolean
# option; default true.) We'd be very grateful if you would leave this on, as
# it helps us to know how many users of CamCOPS there are and what tasks are
# popular. NO PATIENT-IDENTIFIABLE INFORMATION, PER-PATIENT INFORMATION, OR
# TASK DETAILS ARE SENT. If enabled, the following information is sent weekly
# to the CamCOPS base computer:
# - the date/time, including timezone (allowing us to get a rough idea of its
#   geographical distribution)
# - IP address (allowing us to get a rough idea of geographical/institutional
#   distribution)
# - the CamCOPS version number (so we know if old versions are still in use, or
#   if we can break them in an upgrade)
# - the total number of records in each table (allowing us to get an idea of
#   which tasks are popular)

SEND_ANALYTICS = true

# -----------------------------------------------------------------------------
# Export to a staging database for CRIS, CRATE, or similar anonymisation
# software (anonymisation staging database; ANONSTAG)
# -----------------------------------------------------------------------------

ANONSTAG_DB_SERVER = localhost
ANONSTAG_DB_PORT = 3306
ANONSTAG_DB_NAME = anon_staging_camcops
ANONSTAG_DB_USER = UUUUUU_REPLACE_ME
ANONSTAG_DB_PASSWORD = WWWWWW_REPLACE_ME
EXPORT_CRIS_DATA_DICTIONARY_TSV_FILE = /tmp/camcops_cris_dd_draft.tsv

# -----------------------------------------------------------------------------
# In development
# -----------------------------------------------------------------------------

# ALLOW_MOBILEWEB: Enable the mobileweb client? (Boolean.)
# FEATURE IN DEVELOPMENT; SWITCH THIS OFF FOR NOW.
# The Mobile Web app is not yet fully operational.
# Also, enabling Mobile Web allows authorized users to retrieve (their own)
# patient information from the server, so disable it unless you need it.

ALLOW_MOBILEWEB = false

# =============================================================================
# List of HL7/file recipients, and then details for each one
# =============================================================================
# This section defines a list of recipients to which Health Level Seven (HL7)
# messages or raw files will be sent. Typically, you will send them by calling
# "camcops -7 CONFIGFILE" regularly from the system's /etc/crontab or other
# scheduling system. For example, a conventional /etc/crontab file has these
# fields:
#
#   minutes, hours, day_of_month, month, day_of_week, user, command
#
# so you could add a line like this to /etc/crontab:
#
#   * * * * *  root  camcops -7 /etc/camcops/MYCONFIG.conf
#
# ... and CamCOPS would run its exports once per minute. See "man 5 crontab"
# or http://en.wikipedia.org/wiki/Cron for more options.
#
# In this section, keys are ignored; values are section headings (one per
# recipient).

[recipients]

# Examples (commented out):

# recipient=recipient_A
# recipient=recipient_B

# =============================================================================
# Individual HL7/file recipient configurations
# =============================================================================
# Dates are YYYY-MM-DD, e.g. 2013-12-31, or blank

# Example (disabled because it's not in the list above)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# First example
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[recipient_A]

# TYPE: one of the following methods.
#   hl7
#       Sends HL7 messages across a TCP/IP network.
#   file
#       Writes files to a local filesystem.

TYPE = hl7

# -----------------------------------------------------------------------------
# Options applicable to HL7 messages and file transfers
# -----------------------------------------------------------------------------

# PRIMARY_IDNUM: which ID number (1-8) should be considered the "internal"
# (primary) ID number? Must be specified for HL7 messages. May be blank for
# file transmission.

PRIMARY_IDNUM = 1

# REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY: defines behaviour relating to the
# primary ID number (as defined by PRIMARY_IDNUM).
# - If true, no message sending will be attempted unless the PRIMARY_IDNUM is a
#   mandatory part of the finalizing policy (and if FINALIZED_ONLY is false,
#   also of the upload policy).
# - If false, messages will be sent, but ONLY FROM TASKS FOR WHICH THE
#   PRIMARY_IDNUM IS PRESENT; others will be ignored.
# - For file sending only, this will be ignored if PRIMARY_IDNUM is blank.
# - For file sending only, this setting does not apply to anonymous tasks,
#   whose behaviour is controlled by INCLUDE_ANONYMOUS (see below).

REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = true

# START_DATE: earliest date for which tasks will be sent. Assessed against the
# task's "when_created" field, converted to Universal Coordinated Time (UTC) --
# that is, this date is in UTC (beware if you are in a very different time
# zone). Blank to apply no start date restriction.

START_DATE =

# END_DATE: latest date for which tasks will be sent. In UTC. Assessed against
# the task's "when_created" field (converted to UTC). Blank to apply no end
# date restriction.

END_DATE =

# FINALIZED_ONLY: if true, only send tasks that are finalized (moved off their
# originating tablet and not susceptible to later modification). If false, also
# send tasks that are uploaded but not yet finalized (they will then be sent
# again if they are modified later).

FINALIZED_ONLY = true

# TASK_FORMAT: one of: pdf, html, xml

TASK_FORMAT = pdf

# XML_FIELD_COMMENTS: if TASK_FORMAT is xml, then XML_FIELD_COMMENTS determines
# whether field comments are included. These describe the meaning of each field
# so add to space requirements but provide more information for human readers.
# (Default true.)

XML_FIELD_COMMENTS = true

# -----------------------------------------------------------------------------
# Options applicable to HL7 only (TYPE = hl7)
# -----------------------------------------------------------------------------

# HOST: HL7 hostname or IP address

HOST = myhl7server.mydomain

# PORT: HL7 port (default 2575)

PORT = 2575

# PING_FIRST: if true, requires a successful ping to the server prior to
# sending HL7 messages. (Note: this is a TCP/IP ping, and tests that the
# machine is up, not that it is running an HL7 server.) Default: true.

PING_FIRST = true

# NETWORK_TIMEOUT_MS: network time (in milliseconds). Default: 10000.

NETWORK_TIMEOUT_MS = 10000

# IDNUM_TYPE_1 ... IDNUM_TYPE_8: strings used as the HL7 identifier type code
# in the PID segment, field 3 (internal ID) list of identifiers. If one is
# blank, its information will not be sent. (If the PRIMARY_IDNUM's type is
# blank, the system will not process messages.)

IDNUM_TYPE_1 = NHS
IDNUM_TYPE_2 = RiO
IDNUM_TYPE_3 = M
IDNUM_TYPE_4 = Add
IDNUM_TYPE_5 = Pap
IDNUM_TYPE_6 = Hinch
IDNUM_TYPE_7 = PCH
IDNUM_TYPE_8 =

# IDNUM_AA_1 ... IDNUM_AA_8: strings used as the Assigning Authority in the PID
# segment, field 3 (internal ID) list of identifiers. Optional.

IDNUM_AA_1 = NHS
IDNUM_AA_2 = CPFT
IDNUM_AA_3 = CPFT
IDNUM_AA_4 = CUH
IDNUM_AA_5 = CUH
IDNUM_AA_6 = HHC
IDNUM_AA_7 = PSH
IDNUM_AA_8 =

# KEEP_MESSAGE: keep a copy of the entire message in the databaase. Default is
# false. WARNING: may consume significant space in the database.

KEEP_MESSAGE = false

# KEEP_REPLY: keep a copy of the reply (e.g. acknowledgement) message received
# from the server. Default is false. WARNING: may consume significant space.

KEEP_REPLY = false

# DIVERT_TO_FILE: Override HOST/PORT options and send HL7 messages to this
# (single) file instead. Each messages is appended to the file. Default is
# blank (meaning network transmission will be used). This is a debugging
# option, allowing you to redirect HL7 messages to a file and inspect them.

DIVERT_TO_FILE =

# TREAT_DIVERTED_AS_SENT: Any messages that are diverted to a file (using
# DIVERT_TO_FILE) are treated as having been sent (thus allowing the file to
# mimic an HL7-receiving server that's accepting messages happily). If set to
# false (the default), a diversion will allow you to preview messages for
# debugging purposes without "swallowing" them. BEWARE, though: if you have
# an automatically scheduled job (for example, to send messages every minute)
# and you divert with this flag set to false, you will end up with a great many
# message attempts!

TREAT_DIVERTED_AS_SENT = false

# -----------------------------------------------------------------------------
# Options applicable to file transfers only (TYPE = file)
# -----------------------------------------------------------------------------

# INCLUDE_ANONYMOUS: include anonymous tasks.
# - Note that anonymous tasks cannot be sent via HL7; the HL7 specification is
#   heavily tied to identification.
# - Note also that this setting operates independently of the
#   REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY setting.

INCLUDE_ANONYMOUS = true

# PATIENT_SPEC_IF_ANONYMOUS: for anonymous tasks, this string is used as the
# patient descriptor (see also PATIENT_SPEC, FILENAME_SPEC below). Typically
# "anonymous".

PATIENT_SPEC_IF_ANONYMOUS = anonymous

# PATIENT_SPEC: string, into which substitutions will be made, that defines the
# {patient} element available for substitution into the FILENAME_SPEC (see
# below). Possible substitutions: as for PATIENT_SPEC in the main "[server]"
# section of the configuration file (see above).

PATIENT_SPEC = {surname}_{forename}_{idshortdesc1}{idnum1}

# FILENAME_SPEC: string into which substitutions will be made to determine the
# filename to be used for each file. Possible substitutions: as for
# SUGGESTED_PDF_FILENAME in the main "[server]" section of the configuration
# file (see above).

FILENAME_SPEC = /my_nfs_mount/mypath/CamCOPS_{patient}_{created}_{tasktype}-{serverpk}.{filetype}

# MAKE_DIRECTORY: make the directory if it doesn't already exist. Default is
# false.

MAKE_DIRECTORY = true

# OVERWRITE_FILES: whether or not to attempt overwriting existing files of the
# same name (default false). There is a DANGER of inadvertent data loss if you
# set this to true. (Needing to overwrite a file suggests that your filenames
# are not task-unique; try ensuring that both the {tasktype} and {serverpk}
# attributes are used in the filename.)

OVERWRITE_FILES = false

# RIO_METADATA: whether or not to export a metadata file for Servelec's RiO
# (default false).
# Details of this file format are in cc_task.py / Task.get_rio_metadata().
# The metadata filename is that of its associated file, but with the extension
# replaced by ".metadata" (e.g. X.pdf is accompanied by X.metadata).
# If RIO_METADATA is true, the following options also apply:
#   RIO_IDNUM: which of the ID numbers (as above) is the RiO ID?
#   RIO_UPLOADING_USER: username for the uploading user (maximum of 10
#       characters)
#   RIO_DOCUMENT_TYPE: document type as defined in the receiving RiO system.
#       This is a code that maps to a human-readable document type; for
#       example, the code "APT" might map to "Appointment Letter". Typically we
#       might want a code that maps to "Clinical Correspondence", but the code
#       will be defined within the local RiO system configuration.

RIO_METADATA = false
RIO_IDNUM = 2
RIO_UPLOADING_USER = CamCOPS
RIO_DOCUMENT_TYPE = CC

# SCRIPT_AFTER_FILE_EXPORT: filename of a shell script or other executable to
# run after file export is complete. You might use this script, for example, to
# move the files to a different location (such as across a network). If the
# parameter is blank, no script will be run. If no files are exported, the
# script will not be run.
# - Parameters passed to the script: a list of all the filenames exported.
#   (This includes any RiO metadata filenames.)
# - WARNING: the script will execute with the same permissions as the instance
#   of CamCOPS that's doing the export (so, for example, if you run CamCOPS
#   from your /etc/crontab as root, then this script will be run as root; that
#   can pose a risk!).
# - The script executes while the export lock is still held by CamCOPS (i.e.
#   further HL7/file transfers won't be started until the script(s) is/are
#   complete).
# - If the script fails, an error message is recorded, but the file transfer is
#   still considered to have been made (CamCOPS has done all it can and the
#   responsibility now lies elsewhere).
# - Example test script: suppose this is /usr/local/bin/print_arguments:
#       #!/bin/bash
#       for f in $@
#       do
#           echo "CamCOPS has just exported this file: $f"
#       done
#   ... then you could set:
#       SCRIPT_AFTER_FILE_EXPORT = /usr/local/bin/print_arguments

SCRIPT_AFTER_FILE_EXPORT =

    

Use the CamCOPS command-line tool to create tables and a superuser

Run the CamCOPS command-line tool. The tool will need read access to the configuration file (which should be readable only by the Apache user and root). So you might do:

sudo camcops [configfile]

If you didn’t provide the filename of the configuration file, CamCOPS will ask you for it. Enter the name of the configuration you just edited, e.g. /etc/camcops/camcops.conf. Now:

You can also perform configuration tasks from the command line. Type

camcops --help

for details.

Configure Apache or other front-end web server, and interface to back-end web server

  1. Add sections to the HTTPS (SSL) site file (see table above), which is referred to by the main Apache configuration file (see table above). You should read the instructions at /usr/share/camcops/instructions.txt, reproduced below.
    ===============================================================================
    Your system's CamCOPS configuration
    ===============================================================================
    - Default CamCOPS config is:
        /etc/camcops/camcops.conf
      This must be edited before it will run properly.
    
    - Gunicorn/Celery are being supervised as per:
        /etc/supervisor/conf.d/camcops.conf
      This this should be edited to point to the correct CamCOPS config.
      (And copied, changing the CAMCOPS_CONFIG_FILE environment variable, should
      you want to run >1 instance.)
    
    - Gunicorn default port is:
        8006
      To change this, edit
        /etc/supervisor/conf.d/camcops.conf
      (or copy and edit the copy). Duplicate entries in this script to run another
      instance on another port/socket.
    
    - Static file root to serve:
        /usr/share/camcops/server/static
      See instructions below re Apache.
    
    ===============================================================================
    Running CamCOPS tools within its virtual environment
    ===============================================================================
    
    The principle is to use the venv's python executable to run the script.
    
    For example, to run the camcops_meta.py tool to make all tables for all
    databases, assuming all relevant config files are described by
    "/etc/camcops/camcops_*.conf", with sudo prepended to allow access:
    
        sudo /usr/share/camcops/venv/bin/python /usr/share/camcops/server/tools/camcops_meta.py --verbose --filespecs /etc/camcops/camcops_*.conf --ccargs maketables
    
    To test all configs, with the same filespec:
    
        sudo /usr/share/camcops/venv/bin/python /usr/share/camcops/server/tools/camcops_meta.py --verbose --filespecs /etc/camcops/camcops_*.conf --ccargs test
    
    (An alternative method is to use "source /usr/share/camcops/venv/bin/activate",
    and then run things interactively, but this will not work so easily via sudo.)
    
    But a shortcut for all these things is:
    
        sudo /usr/bin/camcops_meta --verbose --filespecs /etc/camcops/camcops_*.conf --ccargs test
    
    ===============================================================================
    Full stack
    ===============================================================================
    
    - Gunicorn serves CamCOPS via WSGI
      ... serving via an internal port (in the default configuration, 8006).
      ... or an internal socket (such as /tmp/.camcops_gunicorn.sock)
    
    - supervisord keeps Gunicorn running
    
    - You should use a proper web server like Apache or nginx to:
    
        (a) serve static files
    
        (b) proxy requests to the WSGI app via Gunicorn
    
    ===============================================================================
    Monitoring with supervisord
    ===============================================================================
    
        sudo supervisorctl  # assuming it's running as root
    
    ===============================================================================
    Advanced configuration
    ===============================================================================
    
    CamCOPS uses operating system environment variables (os.environ, from a Python
    perspective) for things that influence early startup, such as module loading.
    This is because it's often convenient to load all relevant modules before
    reading the configuration file. These govern low-level settings and are not
    typically needed, or are typically set up for you as part of the default
    CamCOPS installation.
    
    Essential environment variables
    -------------------------------
    
    CAMCOPS_CONFIG_FILE
        Points to the configuration file itself (e.g. /etc/camcops.conf).
    
    Common environment variables
    ----------------------------
    
    MPLCONFIGDIR
        A temporary cache directory for matplotlib to store information (e.g. font
        lists). Specifying this dramatically reduces matplotlib's startup time
        (from e.g. 3 seconds the first time, to a fraction of that subsequently).
        If you don't specify it, CamCOPS uses a fresh temporary directory, so you
        don't get the speedup. The default is
            {DSTMPLCONFIGDIR}
        The directory must be writable by the user running CamCOPS.
    
    Debugging environment variables
    -------------------------------
    
    CAMCOPS_DEBUG_TO_HTTP_CLIENT
        Boolean string (e.g. 'True', 'Y', '1' / 'False', 'N', '0').
        Enables exception reporting to the HTTP client. Should be DISABLED for
        production systems. Default is False.
    
    CAMCOPS_PROFILE
        Boolean string.
        Enable a profiling layer on HTTP requests. The output goes to the system
        console. Default is False.
    
    CAMCOPS_SERVE_STATIC_FILES
        Boolean string.
        The CamCOPS program will itself serve static files. Default is True.
        In a production server, you can ignore this setting, but you should serve
        static files from a proper web server (e.g. Apache) instead, for
        performance.
    
    Note regarding environment variables
    ------------------------------------
    
    Operating system environment variables are read at PROGRAM LOAD TIME, not WSGI
    call time. They are distinct from WSGI environment variables (which are passed
    from the WSGI web server to CamCOPS at run-time and contain per-request
    information).
    
    ===============================================================================
    Testing with just gunicorn
    ===============================================================================
    
    - Assuming your www-data has the necessary access, then configure gunicorn
      for a test port on 8000:
    
        sudo -u www-data \
            PYTHONPATH="/usr/share/camcops/server" \
            CAMCOPS_CONFIG_FILE="/etc/camcops/camcops.conf" \
            /usr/share/camcops/venv/bin/gunicorn camcops:application \
            --workers 4 \
            --bind=127.0.0.1:8000
    
    ===============================================================================
    Apache
    ===============================================================================
    -------------------------------------------------------------------------------
    OPTIMAL: proxy Apache through to Gunicorn
    -------------------------------------------------------------------------------
    (a) Add Ubuntu/Apache prerequisites
    
        [Ubuntu]
            sudo apt-get install apache2 libapache2-mod-wsgi libapache2-mod-proxy-html libapache2-mod-xsendfile
            sudo a2enmod proxy_http  # may be unnecessary
            sudo apt-get install mysql-client mysql-server
        [CentOS]
            yum install httpd mod_wsgi mod_proxy mod_xsendfile
            yum install mysql55 mysql55-server libmysqlclient-dev
    
    (b) Configure Apache for CamCOPS.
        Use a section like this in the Apache config file:
    
    <VirtualHost *:443>
        # ...
    
        # =========================================================================
        # CamCOPS
        # =========================================================================
    
            # ---------------------------------------------------------------------
            # 1. Proxy requests to the Gunicorn server and back, and allow access
            # ---------------------------------------------------------------------
            #    ... either via port 8006
            #    ... or, better, via socket /tmp/.camcops_gunicorn.sock
            # NOTES
            # - When you ProxyPass /camcops, you should browse to
            #       https://YOURSITE/camcops/webview
            #   and point your tablets to
            #       https://YOURSITE/camcops/database
            # - Don't specify trailing slashes.
            #   If you do, http://host/camcops will fail though;
            #              http://host/camcops/ will succeed.
            # - Using a socket
            #   - this requires Apache 2.4.9, and passes after the '|' character a
            #     URL that determines the Host: value of the request; see
            #       https://httpd.apache.org/docs/trunk/mod/mod_proxy.html#proxypass
            #   - The Django debug toolbar will then require the bizarre entry in
            #     the Django settings: INTERNAL_IPS = ("b''", ) -- i.e. the string
            #     value of "b''", not an empty bytestring.
            # - Ensure that you put the CORRECT PROTOCOL (e.g. https) in the rules
            #   below.
            # - For ProxyPass options, see https://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypass
            #   ... including "retry=0" to stop Apache disabling the connection for
            #       a while on failure.
    
            # Don't ProxyPass the static files; we'll serve them via Apache.
        ProxyPassMatch ^/camcops/static/ !
    
            # Port
            # Note the use of "http" (reflecting the backend), not https (like the
            # front end).
    
        # ProxyPass /camcops http://127.0.0.1:8006 retry=0
        # ProxyPassReverse /camcops http://127.0.0.1:8006
    
            # Socket (Apache 2.4.9 and higher)
            #
            # The general syntax is:
            #   ProxyPass /URL_USER_SEES unix:SOCKETFILE|PROTOCOL://HOST/EXTRA_URL_FOR_BACKEND retry=0
            # Note that:
            #   - the protocol should be http, not https (Apache deals with the
            #     HTTPS part and passes HTTP on)
            #   - the EXTRA_URL_FOR_BACKEND needs to be (a) unique for each
            #     instance or Apache will use a single worker for multiple
            #     instances, and (b) blank for the backend's benefit. Since those
            #     two conflict when there's >1 instance, there's a problem.
            #   - Normally, HOST is given as localhost. It may be that this problem
            #     is solved by using a dummy unique value for HOST:
            #     https://bz.apache.org/bugzilla/show_bug.cgi?id=54101#c1
            #
            # If your Apache version is too old, you will get the error
            #   "AH00526: Syntax error on line 56 of /etc/apache2/sites-enabled/SOMETHING:
            #    ProxyPass URL must be absolute!"
            # On Ubuntu, if your Apache is too old, you could use
            #   sudo add-apt-repository ppa:ondrej/apache2
            # ... details at https://launchpad.net/~ondrej/+archive/ubuntu/apache2
            #
            # If you get this error:
            #   AH01146: Ignoring parameter 'retry=0' for worker 'unix:/tmp/.camcops_gunicorn.sock|https://localhost' because of worker sharing
            #   https://wiki.apache.org/httpd/ListOfErrors
            # ... then your URLs are overlapping and should be redone or sorted:
            #   http://httpd.apache.org/docs/2.4/mod/mod_proxy.html#workers
            # The part that must be unique for each instance, with no part a
            # leading substring of any other, is THIS_BIT in:
            #   ProxyPass /URL_USER_SEES unix:SOCKETFILE|https://localhost/THIS_BIT retry=0
            #
            # If you get an error like this:
            #   AH01144: No protocol handler was valid for the URL /SOMEWHERE. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule.
            # Then do this:
            #   sudo a2enmod proxy proxy_http
            #   sudo apache2ctl restart
            #
            # If you get an error like this:
            #   ... [proxy_http:error] [pid 32747] (103)Software caused connection abort: [client 109.151.49.173:56898] AH01102: error reading status line from remote server httpd-UDS:0
            #       [proxy:error] [pid 32747] [client 109.151.49.173:56898] AH00898: Error reading from remote server returned by /camcops_bruhl/webview
            # then check you are specifying http://, not https://, in the ProxyPass
            #
            # Other information sources:
            #   https://emptyhammock.com/projects/info/pyweb/webconfig.html
    
        ProxyPass /camcops unix:/tmp/.camcops_gunicorn.sock|http://dummy1/ retry=0
        ProxyPassReverse /camcops unix:/tmp/.camcops_gunicorn.sock|http://dummy1/
    
            # Allow proxy over SSL.
            # Without this, you will get errors like:
            #   ... SSL Proxy requested for wombat:443 but not enabled [Hint: SSLProxyEngine]
            #   ... failed to enable ssl support for 0.0.0.0:0 (httpd-UDS)
    
        SSLProxyEngine on
    
            # Allow access
    
        <Location /camcops>
            Require all granted
        </Location>
    
            # ---------------------------------------------------------------------
            # 2. Serve static files
            # ---------------------------------------------------------------------
            # a) offer them at the appropriate URL
            # b) provide permission
    
        #   Change this: aim the alias at your own institutional logo.
        Alias /camcops/static/logo_local.png /usr/share/camcops/server/static/logo_local.png
    
        #   The rest
        Alias /camcops/static/ /usr/share/camcops/server/static/
    
        <Directory /usr/share/camcops/server/static>
            Require all granted
        </Directory>
    
            # ---------------------------------------------------------------------
            # 3. For additional instances
            # ---------------------------------------------------------------------
            # (a) duplicate section 1 above, editing the base URL and Gunicorn
            #     connection (socket/port);
            # (b) you will also need to create an additional Gunicorn instance,
            #     as above;
            # (c) add additional static aliases (in section 2 above).
            # Example (using sockets):
    
        # ProxyPassMatch ^/camcops_instance2/static/ !
        # ProxyPass /camcops_instance2 unix:/tmp/.camcops_gunicorn_instance2.sock|http://dummy2/ retry=0
        # ProxyPassReverse /camcops_instance2 unix:/tmp/.camcops_gunicorn_instance2.sock|http://dummy2/
        # <Location /camcops_instance2>
        #     Require all granted
        # </Location>
        # Alias /camcops_instance2/static/logo_local.png /usr/share/camcops/server/static/logo_local.png
        # Alias /camcops_instance2/static/ /usr/share/camcops/server/static/
    
    
        #==========================================================================
        # SSL security (for HTTPS)
        #==========================================================================
    
        # You will also need to install your SSL certificate; see the instructions
        # that came with it. You get a certificate by creating a certificate
        # signing request (CSR). You enter some details about your site, and a
        # software tool makes (1) a private key, which you keep utterly private,
        # and (2) a CSR, which you send to a Certificate Authority (CA) for
        # signing. They send back a signed certificate, and a chain of certificates
        # leading from yours to a trusted root CA.
    
        # You can create your own (a 'snake-oil' certificate), but your tablets
        # and browsers will not trust it, so this is a bad idea.
    
        # Once you have your certificate: edit and uncomment these lines:
    
        # SSLEngine on
    
        # SSLCertificateKeyFile /etc/ssl/private/my.private.key
    
            # ... a private file that you made before creating the certificate
            # request, and NEVER GAVE TO ANYBODY, and NEVER WILL (or your
            # security is broken and you need a new certificate).
    
        # SSLCertificateFile /etc/ssl/certs/my.public.cert
    
            # ... signed and supplied to you by the certificate authority (CA),
            # from the public certificate you sent to them.
    
        # SSLCertificateChainFile /etc/ssl/certs/my-institution.ca-bundle
    
            # ... made from additional certificates in a chain, supplied to you by
            # the CA. For example, mine is univcam.ca-bundle, made with the
            # command:
            #
            # cat TERENASSLCA.crt UTNAddTrustServer_CA.crt AddTrustExternalCARoot.crt > univcam.ca-bundle
    
        #==========================================================================
        # GZIP COMPRESSION FOR APPROPRIATE CONTENT TYPES (OPTIONAL)
        # Run "sudo a2enmod deflate" from the command line if not already enabled.
        #==========================================================================
        # http://stackoverflow.com/questions/12367858/how-can-i-get-apache-gzip-compression-to-work
        # testing it: curl -I --compress http://mysite.mydomain/index.html
        # http://serverfault.com/questions/81609/how-to-check-if-apache-compression-is-working
        SetOutputFilter DEFLATE
        AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml application/x-javascript application/x-httpd-php
        BrowserMatch ^Mozilla/4 gzip-only-text/html
        BrowserMatch ^Mozilla/4\.0[678] no-gzip
        BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
        BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
        SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip
        Header append Vary User-Agent env=!dont-vary
    
        
    
  2. You will need to ensure the /etc/supervisor/conf.d/* file(s) are correct. Here’s an example:
  3. 
    # IF YOU EDIT THIS FILE, run:
    #       sudo service supervisor restart
    # TO MONITOR SUPERVISOR, run:
    #       sudo supervisorctl status
    # TO ADD MORE CAMCOPS INSTANCES, make a copy of the [program:camcops-gunicorn]
    #   section, renaming the copy, and change the following:
    #   - the CAMCOPS_CONFIG_FILE environment variable;
    #   - the port or socket;
    #   - the log files.
    # Then make the main web server point to the copy as well.
    # NOTES:
    # - You can't put quotes around the directory variable
    #   http://stackoverflow.com/questions/10653590
    # - Programs like celery and gunicorn that are installed within a virtual
    #   environment use the virtualenv's python via their shebang.
    # - The "environment" setting sets the OS environment. The "--env" parameter
    #   to gunicorn sets the WSGI environment.
    
    # As RPM reinstalls this file (inconveniently), everything is commented out
    # so it doesn't cause disruption in its starting state.
    #
    # Uncomment and edit what you need.
    
    # [program:camcops-gunicorn]
    #
    # command = /usr/share/camcops/venv/bin/gunicorn camcops:application
    #     --workers 4
    #     --bind=unix:/tmp/.camcops_gunicorn.sock
    #     --env CAMCOPS_CONFIG_FILE=/etc/camcops/camcops.conf
    #
    # # Alternative methods (port and socket respectively):
    # #   --bind=127.0.0.1:8006
    # #   --bind=unix:/tmp/.camcops_gunicorn.sock
    # directory = /usr/share/camcops/server
    # environment = PYTHONPATH="/usr/share/camcops/server",MPLCONFIGDIR="/var/cache/camcops/matplotlib"
    # user = www-data
    # # ... Ubuntu: typically www-data
    # # ... CentOS: typically apache
    # stdout_logfile = /var/log/supervisor/camcops_gunicorn.log
    # stderr_logfile = /var/log/supervisor/camcops_gunicorn_err.log
    # autostart = true
    # autorestart = true
    # startsecs = 10
    # stopwaitsecs = 60
    
        
    
  4. On CentOS, the default version (via yum installation) is of supervisord==2.1 (version obtained from pip freeze), which is too old for the [include] directive (which came in with version 3.0). To upgrade:
    pip install requests[security]  # because Python 2.6 doesn't have SSL otherwise
    pip install supervisor==3.2.0
    echo_supervisord_conf > /etc/supervisord.conf  # make a new blank config
    Then add these lines to /etc/supervisord.conf:
    [include]
    files = /etc/supervisor/conf.d/*.conf
    
    Then ensure supervisord restarts on boot. On Ubuntu, this is automatic. On CentOS, run
    sudo chkconfig --add supervisord
    sudo chkconfig supervisord on  # default runlevels (--level 2345) are fine 
  5. Ensure file ownerships/permissions are correct (including, on CentOS, SELinux permissions: see 13PermissionDenied and Apache and SELinux). On Ubuntu, if you use /srv/www as your DocumentRoot, you may need to do:
    sudo chown -R www-data:www-data /srv/www
    
    On CentOS, assuming you use /var/www as your DocumentRoot, you may need to do:
    ls -alZ /var/www # shows owners and SELinux security context
    
    sudo chown -R apache:apache /var/www
    sudo chcon -R -h system_u:object_r:httpd_sys_content_t /var/www
    sudo chown -R apache:apache /etc/camcops
    sudo chcon -R -h system_u:object_r:httpd_sys_content_t /etc/camcops
    sudo chown -R apache:apache /var/cache/camcops
    sudo chcon -R -h system_u:object_r:httpd_sys_content_t /var/cache/camcops
    sudo chown -R apache:apache /usr/share/camcops/server/static
    sudo chcon -R -h system_u:object_r:httpd_sys_content_t /usr/share/camcops/server/static
    
  6. Restart Apache: sudo apachectl restart.
  7. Ensure Apache restarts on boot. On Ubuntu, this should be automatic. On CentOS, run sudo chkconfig --level 2345 httpd on

Test tablet and web access

Test the webview by browsing to the webview URL you configured, by default https://YOURHOST/camcops/webview. Log in with the username and password you chose.

Try with http:// instead, and verify that this fails (you shouldn’t allow access to the CamCOPS server via plain HTTP).

Test the tablet access by pointing your CamCOPS tablet to the tablet URL (by default https://YOURHOST/camcops/database) using a username and password that you have created, registering the tablet, and uploading some data.

Configure backups

Your backup strategy is up to you. However, one option is to use a script to dump all MySQL databases. A sample script is in /usr/share/camcops/demo_mysql_dump_script, reproduced below. You will need to copy this and edit the copy. Be sure your copy of the script is readable only by root, as it contains a password. You can then run your script regularly from /etc/crontab.

#!/bin/sh

# Minimal simple script to dump all current MySQL databases.
# This file must be READABLE ONLY BY ROOT (or equivalent, backup)!
# The password is in cleartext.
# Once you have copied this file and edited it, perform:
#     sudo chown root:root <filename>
#     sudo chmod 700 <filename>
# Then you can add it to your /etc/crontab for regular execution.

BACKUPDIR='/var/backups/mysql'
BACKUPFILE='all_my_mysql_databases.sql'
USERNAME='root'  # MySQL username
PASSWORD='PPPPPP_REPLACE_ME'  # MySQL password

# Make directory unless it exists already:
mkdir -p $BACKUPDIR

# Dump the database:
mysqldump -u $USERNAME -p$PASSWORD --all-databases --force > $BACKUPDIR/$BACKUPFILE

# Make sure the backups (which may contain sensitive information) are only
# readable by the 'backup' user group:
cd $BACKUPDIR
chown -R backup:backup *
chmod -R o-rwx *
chmod -R ug+rw *
    

Obviously, you will also need the dumped files to be backed up to a physically secure location regularly.

More than one CamCOPS instance

This is simple to do. You will need to:

Cosmetics

Your logo

Your logos will be scaled to 45% of the active page width. You may need to add blank space to the left if they look funny. See picture below.

Logo scaling

Performance tuning

MySQL/InnoDB commit

Network latency can be considerably improved by altering the MySQL/InnoDB log-on-commit behaviour. This is governed by the innodb_flush_log_at_trx_commit variable. The default is 1, which is the safest; it is required for ACID compliance. However, setting it to 2 makes database write operations much faster.

This can by done by editing the MySQL configuration file (see table above) to add the line innodb_flush_log_at_trx_commit = 2 in the [mysqld] section, after which you would need to restart MySQL (see commands in table above), or changed dynamically at the MySQL command line with SET GLOBAL innodb_flush_log_at_trx_commit = 2;. Use SHOW VARIABLES; to show the current values.

See discussion, reference.

Uninstalling CamCOPS

Remove prior to any upgrade. See table above for commands.

Upgrading CamCOPS

  1. Stop the web server (see table above).
  2. Uninstall CamCOPS, as above.
  3. Reinstall from the new package (see table above), e.g. sudo gdebi install camcops_NEWVERSION_all.deb or sudo yum install camcops_NEWVERSION.noarch.rpm.
  4. For every configuration file, re-make the tables (in case new tables have been added): sudo camcops -m /etc/camcops/myconfigfile.
    • If you have lots, you can use the camcops_meta.py tool, like this: sudo camcops_meta --filespecs /etc/camcops/SOMEFILESPEC --ccargs maketables
  5. Restart Apache (see table above).

Troubleshooting

Valid HTML 4.01 Transitional
Valid CSS