> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/appertafoundation/openeyes/llms.txt
> Use this file to discover all available pages before exploring further.

# Production Installation

> Complete guide for installing and configuring OpenEyes in production environments

## Overview

This guide covers installing OpenEyes for production use. For development environments, see the [Quick Start Guide](/quickstart).

<Warning>
  **Important Safety Notice**

  OpenEyes is provided under AGPL v3.0 license with NO WARRANTY. Any user deploying OpenEyes in a clinical environment must:

  * Undertake a comprehensive safety assessment
  * Ensure deployment matches local clinical safety requirements
  * Comply with relevant data protection regulations (GDPR, HIPAA, etc.)
  * Implement proper backup and disaster recovery procedures
  * Obtain appropriate support contracts for clinical use
</Warning>

## System Requirements

### Server Requirements

<Tabs>
  <Tab title="Operating System">
    * **Recommended**: Ubuntu 20.04 LTS or 22.04 LTS
    * RHEL/CentOS 7/8 (with adjustments)
    * Debian 10/11
  </Tab>

  <Tab title="Hardware">
    **Minimum**:

    * 2 CPU cores
    * 4GB RAM
    * 50GB disk space

    **Recommended for Production**:

    * 4+ CPU cores
    * 8GB+ RAM
    * 100GB+ SSD storage
    * Additional storage for backups
  </Tab>

  <Tab title="Network">
    * Static IP address
    * Firewall access for HTTP (80) and HTTPS (443)
    * Optional: SSH (22) for management
    * Internal network access to PAS/HL7 systems (if applicable)
  </Tab>
</Tabs>

### Software Requirements

Based on `composer.json` specifications:

<CodeGroup>
  ```json composer.json theme={null}
  {
    "require": {
      "php": "^7.4 || ^8.0 || ^8.1",
      "ext-json": "^7.4 || ^8.0 || ^8.1",
      "ext-dom": "*",
      "ext-libxml": "*",
      "ext-openssl": "*",
      "ext-soap": "*",
      "yiisoft/yii": "1.1.28"
    }
  }
  ```
</CodeGroup>

**Required PHP Extensions**:

* `php-json`
* `php-dom`
* `php-xml` / `php-libxml`
* `php-openssl`
* `php-soap`
* `php-mysql` or `php-mysqli`
* `php-gd`
* `php-curl`
* `php-mbstring`

**Database**:

* MySQL 5.7+ or MariaDB 10.3+
* InnoDB storage engine
* 128MB+ `innodb_log_file_size`
* 128MB+ `max_allowed_packet`

**Web Server**:

* Apache 2.4+ with mod\_rewrite
* OR Nginx 1.18+

## Installation Steps

<Steps>
  <Step title="Install System Dependencies">
    Update system and install required packages:

    ```bash theme={null}
    # Update system
    sudo apt update && sudo apt upgrade -y

    # Install PHP 8.1 and extensions
    sudo apt install -y php8.1 php8.1-cli php8.1-fpm \
      php8.1-mysql php8.1-json php8.1-xml php8.1-soap \
      php8.1-gd php8.1-curl php8.1-mbstring php8.1-dom

    # Install Apache
    sudo apt install -y apache2

    # Enable required Apache modules
    sudo a2enmod rewrite ssl headers

    # Install MySQL
    sudo apt install -y mysql-server-8.0

    # Install Composer
    curl -sS https://getcomposer.org/installer | sudo php -- \
      --install-dir=/usr/local/bin --filename=composer

    # Install Git
    sudo apt install -y git
    ```

    Verify PHP version:

    ```bash theme={null}
    php -v  # Should show 7.4, 8.0, or 8.1
    ```
  </Step>

  <Step title="Configure MySQL Database">
    Create the OpenEyes database and user:

    ```bash theme={null}
    # Connect to MySQL
    sudo mysql
    ```

    Execute the following SQL:

    ```sql theme={null}
    -- Create database
    CREATE DATABASE openeyes CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

    -- Create user with strong password
    CREATE USER 'openeyes'@'localhost' IDENTIFIED BY 'STRONG_PASSWORD_HERE';

    -- Grant privileges
    GRANT ALL PRIVILEGES ON openeyes.* TO 'openeyes'@'localhost';
    FLUSH PRIVILEGES;

    -- Exit MySQL
    EXIT;
    ```

    Configure MySQL for OpenEyes by editing `/etc/mysql/mysql.conf.d/mysqld.cnf`:

    ```ini theme={null}
    [mysqld]
    innodb_log_file_size = 128M
    max_allowed_packet = 128M
    net_buffer_length = 1000000

    # For production, enable binary logging
    log_bin = /var/log/mysql/mysql-bin.log
    binlog_expire_logs_seconds = 604800
    ```

    Restart MySQL:

    ```bash theme={null}
    sudo systemctl restart mysql
    ```

    <Warning>
      Replace `STRONG_PASSWORD_HERE` with a secure password. Store it securely for later configuration.
    </Warning>
  </Step>

  <Step title="Clone OpenEyes Repository">
    Clone to the web server directory:

    ```bash theme={null}
    # Create directory
    sudo mkdir -p /var/www/openeyes
    cd /var/www/openeyes

    # Clone repository (stable master branch)
    sudo git clone -b master https://github.com/AppertaFoundation/openeyes.git .

    # Set ownership
    sudo chown -R www-data:www-data /var/www/openeyes
    ```

    For a specific version:

    ```bash theme={null}
    git checkout tags/v3.0  # Replace with desired version
    ```
  </Step>

  <Step title="Install PHP Dependencies">
    Install dependencies using Composer:

    ```bash theme={null}
    cd /var/www/openeyes
    sudo -u www-data composer install --no-dev --optimize-autoloader
    ```

    <Note>
      The `--no-dev` flag excludes development dependencies for production.
    </Note>
  </Step>

  <Step title="Configure Database Connection">
    OpenEyes reads database configuration from environment variables or `/etc/openeyes/db.conf`.

    **Option 1: Environment Variables** (Recommended for Docker/containerized deployments)

    Set in your environment or systemd service:

    ```bash theme={null}
    export DATABASE_HOST=localhost
    export DATABASE_PORT=3306
    export DATABASE_NAME=openeyes
    export DATABASE_USER=openeyes
    export DATABASE_PASS=STRONG_PASSWORD_HERE
    ```

    **Option 2: Configuration File** (Traditional deployment)

    Create `/etc/openeyes/db.conf`:

    ```ini theme={null}
    host=localhost
    port=3306
    dbname=openeyes
    username=openeyes
    password=STRONG_PASSWORD_HERE
    ```

    Secure the file:

    ```bash theme={null}
    sudo chown www-data:www-data /etc/openeyes/db.conf
    sudo chmod 600 /etc/openeyes/db.conf
    ```

    The configuration is read from `protected/config/core/common.php`:

    ```php theme={null}
    if (file_exists('/etc/openeyes/db.conf')) {
        $db = parse_ini_file('/etc/openeyes/db.conf');
    } else {
        $db = array(
            'host' => getenv('DATABASE_HOST') ?: 'localhost',
            'port' => getenv('DATABASE_PORT') ?: '3306',
            'dbname' => getenv('DATABASE_NAME') ?: 'openeyes',
            'username' => getenv('DATABASE_USER') ?: 'openeyes',
            'password' => getenv('DATABASE_PASS') ?: 'openeyes',
        );
    }
    ```
  </Step>

  <Step title="Run Database Migrations">
    Initialize the database schema:

    ```bash theme={null}
    cd /var/www/openeyes
    sudo -u www-data php protected/yiic.php migrate --interactive=0
    ```

    This creates all necessary tables and applies schema migrations.

    For module-specific migrations:

    ```bash theme={null}
    sudo -u www-data php protected/yiic.php migrate \
      --migrationPath=application.modules.OphCiExamination.migrations
    ```
  </Step>

  <Step title="Set File Permissions">
    Configure proper ownership and permissions:

    ```bash theme={null}
    cd /var/www/openeyes

    # Set ownership
    sudo chown -R www-data:www-data /var/www/openeyes

    # Runtime directory needs write access
    sudo chmod -R 775 protected/runtime
    sudo chmod -R 775 assets

    # Protect sensitive files
    sudo chmod 640 protected/config/local/*.php
    ```
  </Step>

  <Step title="Configure Apache Virtual Host">
    Create `/etc/apache2/sites-available/openeyes.conf`:

    ```apache theme={null}
    <VirtualHost *:80>
        ServerName openeyes.example.com
        ServerAdmin admin@example.com
        
        DocumentRoot /var/www/openeyes
        
        <Directory /var/www/openeyes>
            Options -Indexes +FollowSymLinks
            AllowOverride All
            Require all granted
            
            # Yii framework rewrite rules
            RewriteEngine On
            RewriteBase /
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteCond %{REQUEST_FILENAME} !-d
            RewriteRule . /index.php [L]
        </Directory>
        
        # Protect sensitive directories
        <Directory /var/www/openeyes/protected>
            Require all denied
        </Directory>
        
        # Logging
        ErrorLog ${APACHE_LOG_DIR}/openeyes_error.log
        CustomLog ${APACHE_LOG_DIR}/openeyes_access.log combined
        
        # Security headers
        Header always set X-Frame-Options "SAMEORIGIN"
        Header always set X-Content-Type-Options "nosniff"
        Header always set X-XSS-Protection "1; mode=block"
    </VirtualHost>
    ```

    Enable the site:

    ```bash theme={null}
    sudo a2ensite openeyes
    sudo systemctl reload apache2
    ```
  </Step>

  <Step title="Configure SSL/TLS">
    For production, HTTPS is mandatory. Use Let's Encrypt:

    ```bash theme={null}
    # Install Certbot
    sudo apt install -y certbot python3-certbot-apache

    # Obtain certificate
    sudo certbot --apache -d openeyes.example.com
    ```

    Certbot will automatically:

    * Obtain SSL certificate
    * Configure Apache for HTTPS
    * Set up automatic renewal

    For manual SSL configuration, create `/etc/apache2/sites-available/openeyes-ssl.conf`:

    ```apache theme={null}
    <VirtualHost *:443>
        ServerName openeyes.example.com
        
        DocumentRoot /var/www/openeyes
        
        SSLEngine on
        SSLCertificateFile /etc/ssl/certs/openeyes.crt
        SSLCertificateKeyFile /etc/ssl/private/openeyes.key
        SSLCertificateChainFile /etc/ssl/certs/ca-bundle.crt
        
        # Modern SSL configuration
        SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
        SSLCipherSuite HIGH:!aNULL:!MD5
        SSLHonorCipherOrder on
        
        # HSTS
        Header always set Strict-Transport-Security "max-age=31536000"
        
        # Include directory configuration from HTTP vhost
        Include /etc/apache2/sites-available/openeyes.conf
    </VirtualHost>
    ```
  </Step>

  <Step title="Configure Application Settings">
    Set production environment variables:

    ```bash theme={null}
    # In Apache envvars or systemd unit
    export OE_MODE=LIVE
    export OE_INSTITUTION_CODE=YOUR_INSTITUTION_CODE
    export AUTH_SOURCE=BASIC  # or LDAP, SAML, OIDC
    ```

    Key configuration options from `protected/config/core/common.php`:

    | Variable              | Default | Description                                     |
    | --------------------- | ------- | ----------------------------------------------- |
    | `OE_MODE`             | -       | Operating mode: `LIVE`, `TEST`, or `DEV`        |
    | `OE_INSTITUTION_CODE` | `NEW`   | Your institution identifier                     |
    | `AUTH_SOURCE`         | `BASIC` | Authentication: `BASIC`, `LDAP`, `SAML`, `OIDC` |
    | `SSO_CLIENT_ID`       | -       | OIDC client ID (if using SSO)                   |
    | `SSO_CLIENT_SECRET`   | -       | OIDC client secret (if using SSO)               |
    | `OE_PASAPI_ENABLE`    | `FALSE` | Enable PAS API integration                      |
    | `OE_PORTAL_ENABLED`   | `FALSE` | Enable patient portal                           |

    <Warning>
      For production, **always** set `OE_MODE=LIVE` to disable debug features and enforce security.
    </Warning>
  </Step>

  <Step title="Create Initial Admin User">
    Access the database and create an administrative user:

    ```bash theme={null}
    mysql -u openeyes -p openeyes
    ```

    ```sql theme={null}
    -- Insert admin user (adjust as needed)
    INSERT INTO user (username, first_name, last_name, email, 
                      active, password_softlocked, password_status)
    VALUES ('admin', 'System', 'Administrator', 'admin@example.com', 
            1, 0, 0);

    -- Set password (will need to be changed on first login)
    UPDATE user SET password = MD5('ChangeMe123!') 
    WHERE username = 'admin';

    -- Grant admin role
    INSERT INTO authassignment (itemname, userid)
    SELECT 'admin', id FROM user WHERE username = 'admin';
    ```

    <Note>
      The user will be prompted to change their password on first login.
    </Note>
  </Step>

  <Step title="Verify Installation">
    Test the installation:

    1. **Access the application**:
       * Navigate to `https://openeyes.example.com`
       * You should see the OpenEyes login page

    2. **Check PHP configuration**:
       ```bash theme={null}
       sudo -u www-data php protected/yiic.php version
       ```

    3. **Verify database connection**:
       ```bash theme={null}
       sudo -u www-data php protected/yiic.php migrate --interactive=0
       ```
       Should report "No new migrations found."

    4. **Review logs**:
       ```bash theme={null}
       tail -f /var/log/apache2/openeyes_error.log
       ```
  </Step>
</Steps>

## Post-Installation Configuration

### Authentication Setup

<Tabs>
  <Tab title="LDAP Authentication">
    Configure LDAP integration:

    ```bash theme={null}
    export AUTH_SOURCE=LDAP
    export OE_LDAP_SERVER=ldap://ldap.example.com
    export OE_LDAP_PORT=389
    export OE_LDAP_ADMIN_DN="cn=admin,dc=example,dc=com"
    export OE_LDAP_PASSWORD="ldap_password"
    export OE_LDAP_BASE_DN="ou=users,dc=example,dc=com"
    ```
  </Tab>

  <Tab title="SAML SSO">
    Configure SAML authentication:

    ```bash theme={null}
    export AUTH_SOURCE=SAML
    export SSO_BASE_URL=https://openeyes.example.com
    export SSO_ENTITY_ID=https://openeyes.example.com/saml/metadata
    export SSO_APP_EMBED_LINK=https://sso.example.com/login
    ```

    Place IdP metadata in `protected/config/local/saml/`
  </Tab>

  <Tab title="OpenID Connect">
    Configure OIDC authentication:

    ```bash theme={null}
    export AUTH_SOURCE=OIDC
    export SSO_PROVIDER_URL=https://sso.example.com
    export SSO_CLIENT_ID=openeyes
    export SSO_CLIENT_SECRET=your_client_secret
    export SSO_REDIRECT_URL=https://openeyes.example.com/oidc/callback
    ```
  </Tab>
</Tabs>

### Cron Jobs

Set up scheduled tasks:

```bash theme={null}
sudo crontab -u www-data -e
```

Add the following:

```cron theme={null}
# OpenEyes scheduled tasks
*/5 * * * * /usr/bin/php /var/www/openeyes/protected/yiic.php cronrunner

# Database backup (daily at 2 AM)
0 2 * * * /var/www/openeyes/protected/scripts/backupdb.sh

# Log rotation
0 0 * * 0 /usr/sbin/logrotate /etc/logrotate.d/openeyes
```

### Backup Strategy

Implement regular backups:

```bash backup.sh theme={null}
#!/bin/bash
# OpenEyes Backup Script

BACKUP_DIR="/backup/openeyes"
DATE=$(date +%Y%m%d_%H%M%S)

# Database backup
mysqldump -u openeyes -p$DB_PASSWORD openeyes | \
  gzip > $BACKUP_DIR/db_$DATE.sql.gz

# File backup
tar -czf $BACKUP_DIR/files_$DATE.tar.gz \
  /var/www/openeyes/protected/runtime \
  /var/www/openeyes/protected/files \
  /etc/openeyes/

# Retain last 30 days
find $BACKUP_DIR -mtime +30 -delete
```

## Security Hardening

<AccordionGroup>
  <Accordion title="Firewall Configuration">
    Configure UFW firewall:

    ```bash theme={null}
    sudo ufw allow 22/tcp   # SSH
    sudo ufw allow 80/tcp   # HTTP
    sudo ufw allow 443/tcp  # HTTPS
    sudo ufw enable
    ```
  </Accordion>

  <Accordion title="PHP Security">
    Edit `/etc/php/8.1/apache2/php.ini`:

    ```ini theme={null}
    expose_php = Off
    display_errors = Off
    log_errors = On
    error_log = /var/log/php/error.log

    upload_max_filesize = 128M
    post_max_size = 128M
    memory_limit = 256M

    disable_functions = exec,passthru,shell_exec,system,proc_open
    ```
  </Accordion>

  <Accordion title="Database Security">
    * Use strong passwords
    * Restrict database user to localhost only
    * Enable binary logging for point-in-time recovery
    * Regular security updates

    ```bash theme={null}
    # Remove anonymous users
    mysql -e "DELETE FROM mysql.user WHERE User=''"

    # Remove test database
    mysql -e "DROP DATABASE IF EXISTS test"

    # Flush privileges
    mysql -e "FLUSH PRIVILEGES"
    ```
  </Accordion>

  <Accordion title="File Integrity Monitoring">
    Install and configure AIDE:

    ```bash theme={null}
    sudo apt install aide
    sudo aideinit
    sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

    # Run daily checks
    echo '0 3 * * * root /usr/bin/aide --check' | sudo tee -a /etc/crontab
    ```
  </Accordion>
</AccordionGroup>

## Performance Optimization

### PHP OpCache

Enable and configure OpCache in `/etc/php/8.1/apache2/php.ini`:

```ini theme={null}
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
```

### MySQL Tuning

Optimize MySQL configuration:

```ini theme={null}
[mysqld]
innodb_buffer_pool_size = 2G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
query_cache_size = 64M
tmp_table_size = 64M
max_heap_table_size = 64M
```

### Apache MPM

Configure Apache MPM prefork:

```apache theme={null}
<IfModule mpm_prefork_module>
    StartServers             5
    MinSpareServers          5
    MaxSpareServers         10
    MaxRequestWorkers      150
    MaxConnectionsPerChild   0
</IfModule>
```

## Monitoring and Maintenance

### Application Monitoring

```bash theme={null}
# Monitor Apache processes
sudo systemctl status apache2

# Check PHP-FPM status
sudo systemctl status php8.1-fpm

# Monitor MySQL
sudo systemctl status mysql

# View application logs
tail -f /var/www/openeyes/protected/runtime/application.log
```

### Health Checks

Create a monitoring script:

```bash monitor.sh theme={null}
#!/bin/bash
# Check if OpenEyes is responding

URL="https://openeyes.example.com"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" $URL)

if [ $HTTP_CODE -eq 200 ]; then
    echo "OpenEyes is healthy"
else
    echo "OpenEyes returned HTTP $HTTP_CODE"
    # Send alert
fi
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="White Screen / 500 Error">
    Check PHP error logs:

    ```bash theme={null}
    tail -f /var/log/apache2/openeyes_error.log
    tail -f /var/www/openeyes/protected/runtime/application.log
    ```

    Common causes:

    * Database connection failure
    * File permission issues
    * Missing PHP extensions
  </Accordion>

  <Accordion title="Database Migration Errors">
    Reset migration state:

    ```bash theme={null}
    mysql -u openeyes -p openeyes -e "SELECT * FROM migration_history ORDER BY apply_time DESC LIMIT 10;"
    ```

    Manually apply failed migration:

    ```bash theme={null}
    sudo -u www-data php protected/yiic.php migrate redo
    ```
  </Accordion>

  <Accordion title="Session Issues">
    Clear session data:

    ```bash theme={null}
    sudo rm -rf /var/www/openeyes/protected/runtime/sessions/*
    sudo systemctl restart apache2
    ```
  </Accordion>

  <Accordion title="Performance Issues">
    Enable slow query log:

    ```sql theme={null}
    SET GLOBAL slow_query_log = 'ON';
    SET GLOBAL long_query_time = 2;
    ```

    Review slow queries:

    ```bash theme={null}
    tail -f /var/log/mysql/mysql-slow.log
    ```
  </Accordion>
</AccordionGroup>

## Upgrading OpenEyes

<Warning>
  Always backup before upgrading!
</Warning>

<Steps>
  <Step title="Backup Current Installation">
    ```bash theme={null}
    # Database
    mysqldump -u openeyes -p openeyes > backup_$(date +%F).sql

    # Files
    tar -czf openeyes_backup_$(date +%F).tar.gz /var/www/openeyes
    ```
  </Step>

  <Step title="Pull Latest Code">
    ```bash theme={null}
    cd /var/www/openeyes
    sudo -u www-data git fetch --tags
    sudo -u www-data git checkout tags/v3.1  # Or desired version
    ```
  </Step>

  <Step title="Update Dependencies">
    ```bash theme={null}
    sudo -u www-data composer install --no-dev --optimize-autoloader
    ```
  </Step>

  <Step title="Run Migrations">
    ```bash theme={null}
    sudo -u www-data php protected/yiic.php migrate --interactive=0
    ```
  </Step>

  <Step title="Clear Cache">
    ```bash theme={null}
    sudo rm -rf /var/www/openeyes/protected/runtime/cache/*
    sudo rm -rf /var/www/openeyes/assets/*
    sudo systemctl restart apache2
    ```
  </Step>
</Steps>

## Getting Support

<CardGroup cols={2}>
  <Card title="GitHub Issues" icon="github" href="https://github.com/AppertaFoundation/openeyes/issues">
    Community issue tracker
  </Card>

  <Card title="Wiki Documentation" icon="book" href="https://github.com/AppertaFoundation/openeyes/wiki">
    Additional installation guides
  </Card>

  <Card title="Professional Services" icon="briefcase" href="https://openeyes.apperta.org">
    Accredited partners for production support
  </Card>

  <Card title="Contact" icon="envelope" href="mailto:openeyes@apperta.org">
    General inquiries
  </Card>
</CardGroup>

<Note>
  **Production Deployment Checklist**

  * [ ] SSL/TLS certificate configured
  * [ ] Firewall rules applied
  * [ ] Database backups automated
  * [ ] Cron jobs configured
  * [ ] Monitoring enabled
  * [ ] Security hardening completed
  * [ ] Admin users created
  * [ ] Authentication configured (LDAP/SAML/OIDC)
  * [ ] Clinical safety assessment performed
  * [ ] Support contract in place
  * [ ] Disaster recovery plan documented
</Note>
