setting up the server

These are the requirements for running InterpretersOffice. My intention is make installation easier than what is described below, with less manual setup involved, but for now illud est quod est. And of course, if you already have your web and database servers installed, there is less to do. These instructions assume a server with a Linux OS and little else.

web server: Apache or Nginx

Since I’m personally more familiar with Apache, we’ll assume the use of Apache.

  1. Install the web server.
  2. Set up the virtual host.
    • Decide whether you want to allow per-directory overrides (with a .htaccess file) and put the url-rewriting recipe there, or else put it in the vhost configuration and (optionally) disable overrides. The latter strategy gives you marginally better performance, at the cost of having to reload the server should you need to change this configuration.
    • Set as web document root the full path to public subdirectory under the application root.
    • Set an environment variable called environment to: production.
  3. Enable mod_rewrite (or equivalent)
    This is necessary because InterpretersOffice uses an MVC framework that depends on URL rewriting to route requests to the proper controller/action.

php support

  1. Install php7.4-fpm. (The minimum supported version is 7.3)
  2. Install php extensions: php7.4‑mysql, php7.4‑curl, php7.4‑mbstring, php7.4‑json, php7.4‑dom, php7.4‑intl
  3. Install php composer

database server: mariadb or mysql

  1. Install the database server.
    You can use either mysql or mariadb, but if you opt to set up master-server replication, it is recommended that you choose one or the other exclusively. I have been happy with both but currently my preference is mariadb.
  2. Create the database.
    You can call it what you like, but the name I have been using is office.
  3. Create the database user and grant privileges:
    CREATE USER 'interpreters'@'localhost' IDENTIFIED BY 'their_password';
    GRANT ALL PRIVILEGES ON `office`.* TO 'interpreters'@'localhost';

installing the application

get the code and its dependencies

Install git if you haven’t already. This step also requires the the php dependency manager composer.

cd /path/to/application/
git clone
cd court-interpreters-office
composer install

You can safely ignore any warning about package container-interop/container-interop having been abandoned. Moving on:

set up the data directory

mkdir -p data/log
mkdir data/session
mkdir data/cache

Make these writeable by the web server, e.g.,

setfacl -Rm u:www-data:rwx data

set up the database

Create and edit the database configuration file:

cd config/autoload
cp doctrine.local.php.dist doctrine.local.php

Open doctrine.local.php in a text editor, set the values for user, password, and host appropriately, and save.

You should now be able to load the index page in a browser without incident, but there is still more to do, starting with database initialization.

If you are migrating from an existing installation, simply mysqldump the old database into the new one, and your database is good to go. (After first shutting off access the old installation, to avoid losing data in the transition.)

If you are starting from scratch with an empty database, you need to seed the database by running SQL scripts found in bin/sql. From your application root,

cat bin/sql/mysql-schema.sql bin/sql/initial-data.sql | mysql -p  -h your_host office

Next, you need to set up an initial administrative user, using the provided interactive CLI script. From the application root,

bin/admin-cli setup:create-admin-user

Supply the answers as prompted, and you’ll have an admin user who can log in and carry on using the web interface. (Please note: at this point it is technically possible to move forward with the database as is, but there are several data tables that would have to be populated by the admin users, row by row, and it would be tedious. It’s worth considering writing some one-off scripts to import data from existing sources. Feel free to contact to discuss.)


InterpretersOffice relies heavily on outbound email. Copy the file config/autoload/local.production.php.dist to config/autoload/local.production.php, and then edit the mail section:


use Laminas\Mail\Transport\Smtp;
use Laminas\Mail\Transport\SmtpOptions;

return [
    // stuff omitted...
    'mail' => [
        'transport' => Smtp::class,
        'transport_options' => [
            'class' => SmtpOptions::class,
            'options' => [
                'name'     => 'your.stmp.server',
                'host'     => 'host_ip_address',
                'port'     => 465,
                'connection_class'  => 'login',

                'connection_config' => [
                    'username' => 'your_username',
                    'password' => 'your_password',
                    'ssl'  => 'ssl',
        'from_address' => '',
        'from_entity' => 'Interpreters Office',

replacing all the values appropriately. This array has to contain configuration that a Laminas\Mail transport class constructor can consume. It does not necessarily have to be the SMTP transport per se. If, for example, your system has postfix configured to relay to an SMTP server, you can use Laminas\Mail\Transport\Sendmail instead:

'mail' => [
        'transport' => 'Laminas\Mail\Transport\Sendmail',
        'transport_options' => [        
            // optional

Set the values of from_address and from_entity appropriately; these are the defaults used to populate the From: header of outgoing messages.

If you want to use Mailgun, put in the config/autoload directory a configuration file called mailgun.local.php with contents like

return [
    'mailgun' => [
      'smtp' => [
          'user' => '',
          'password' => 'your_password',
          'host' => '',
      'api' => [
        'key' => 'your_api_key',        
        'base_url' => '',
        'domain'=>  '',

and then Mailgun’s SMTP services will be used for sending most emails, but the Mailgun API will be used for the batch email feature (which allows administrative users to send email en masse to particular groups, such as all active contract interpreters).

other configuration

There is some more configuration data to be set manually because InterpretersOffice does not yet provide a GUI for all the application configuration.

In config/autoload/local.production.php there a couple more sections to be edited.

/** contact information variables for your layout */
'site' => [
    'contact' => [
        'organization_name' => 'Your Office',
        'organization_locality' => 'Your City',
        'telephone' => 'Your phone number',
        'email' => '',
        'website' => '',
*   optional list of IP addresses from which users can read the interpreters schedule 
*   without logging in, and/or strings one of which the hosting domain must match
'permissions' => [
    'schedule' => [
        'anonymous_ips_allowed' => [],
        'host_domains_allowed' => [],
'security' => [
    'max_login_failures' => 6,        

The contact array is injected into the main layout and into some email templates.

The permissions settings are used to determine under what conditions, if any, read-access to the Interpreters schedule is allowed to anonymous users. The value of this is for contract interpreters who legitimately need to see the schedule, but since they do not have user accounts they can never log in.

If the host domain is one you know to be running on a private network, anyone using the application is presumptively authorized to be inside that network, so you may wish to enable anonymous schedule access by adding that host name to the host_domains_allowed array. The anonymous_ips_allowed applies similar logic in reverse. If, for example, you know a particular IP address to be that of the gateway in front of a private network, you may wish to add it to the anonymous_ips_allowed array.

If you simply leave this permissions array untouched, all users will be required to be authenticated in order to view the Interpreters schedule.

The max_login_failures variable refers to how many consecutive failed logins are permitted before a user account is disabled. You can set this value pretty high if you like, but currently you cannot set it to zero to mean unlimited. The default (six) seems sensible.

There are a couple more configuration files that have to be present and writeable by the server: module/Admin/config/forms.json and module/Rotation/config/config.json. In the Interpreters Office for the Southern District of New York, the former configuration looks like

    "interpreters": {
        "optional_elements": {
            "banned_by_persons": "1",
            "BOP_form_submission_date": "1",
            "fingerprint_date": "1",
            "contract_expiration_date": "1",
            "oath_date": "1",
            "security_clearance_date": "1"
    "events": {
        "optional_elements": {
            "end_time": "1"
    "users" : {
        "allow_email_domains" : [

The first two sections are for enabling/disabling optional fields for interpreter entities and event entities.

The users section is a whitelist of email domains users of the system are permitted to have, preventing users from registering accounts from arbitrary email addresses. It also imposes validation rules for the user profile and administrative user editing forms. If you want to disable this, leave the allow_email_domains section as an empty array.

Additionally, the subdirectory module/Requests/config has to be server-writeable.

Hashicorp Vault for sensitive data

InterpretersOffice includes an optional module called Vault. If you intend to handle sensitive contract interpreter data (dates of birth and social security numbers), the Vault module must be configured and enabled. A prerequisite is an installation Hashicorp Vault itself. Initial setup of the InterpretersOffice Vault module is a chore, though the increased security is more than worth the effort. We have some notes here but they need updating, as Vault development keeps moving forward.

There is a blog post about our Vault integration with InterpretersOffice that explains how it works. Briefly, the interpreters’ sensitive data at rest is symmetrically encryted, and the cipher for encryption/decryption is secured in Vault rather than lying around in plain text. In other words the encryption/decryption key is never stored anywhere on the server in plain text.