# Eeva for PHP / cPanel

This edition runs the Eeva assistant and its API on PHP shared hosting. The browser assets are already built. **Node.js, npm, Composer and a background worker are not required on your hosting account.**

## 1. Hosting requirements

- PHP **8.2 or newer**, selected for the assistant domain in cPanel → MultiPHP Manager.
- PHP extensions: **curl, mbstring, fileinfo**. Enable **pdo_mysql** only for optional MySQL storage.
- HTTPS certificate, outbound HTTPS access to api.openai.com and a writable private directory.
- Apache/LiteSpeed with .htaccess support. Some hosts buffer streamed output; see troubleshooting below.
- An OpenAI API key with access to the configured models. API usage is billed to that account.

## 2. Upload the ZIP

Extract eeva-cpanel.zip in your cPanel account's **home directory**, one level ABOVE public_html. Enable “Show Hidden Files” in File Manager so .htaccess and .user.ini are copied.

The final layout must be:

```
/home/CPANEL_USER/
├── eeva-private/
│   ├── config.example.php
│   ├── config.php              ← create in step 3
│   ├── bootstrap.php
│   ├── system-prompt.txt
│   ├── schema.sql
│   ├── src/
│   ├── bin/
│   └── data/
└── public_html/
    └── eeva/
        ├── index.php
        ├── api.php
        ├── embed.js
        ├── embed.css
        ├── embed-example.html
        ├── assets/
        ├── .htaccess
        └── .user.ini
```

Keep **eeva-private outside every public document root**. Do not upload the source repository or ZIP into a public website directory.

For a normal domain this creates https://YOUR-DOMAIN/eeva/. The public PHP files look for eeva-private two levels above their own folder. For a different subdomain/document-root layout, change the $private path near the top of BOTH index.php and api.php to the absolute private directory, or set the EEVA_PRIVATE_DIR environment variable.

## 3. Configure

Copy eeva-private/config.example.php to eeva-private/config.php and edit:

| Setting | Value |
| --- | --- |
| app_url | Full HTTPS URL, such as https://YOUR-DOMAIN/eeva/ |
| openai_api_key | Your server-side OpenAI API key |
| admin_token | Unique random secret, at least 32 characters |
| vector_store_id | Your OpenAI vector store ID containing approved Eezix information |
| embed_origins | Exact website origins, e.g. ['https://www.yoursite.com','https://yoursite.com'] |
| human_support_url | Your actual HTTPS contact/support page |
| storage | Keep file for the simplest installation |

Leave session_secret blank to generate it automatically in private storage. A secret can be generated in cPanel Terminal with:

```sh
php -r "echo bin2hex(random_bytes(32)), PHP_EOL;"
```

Use permissions appropriate to your PHP account: private directories 700 and private files 600 usually work with PHP-FPM. The PHP process must be able to read config.php and write eeva-private/data. Do not use 777.

## 4. Open and configure Eeva

- Assistant: https://YOUR-DOMAIN/eeva/
- Admin: https://YOUR-DOMAIN/eeva/index.php?admin=1
- Health: https://YOUR-DOMAIN/eeva/api.php?route=/health

Sign into Admin with admin_token. Configure suggested questions, voice and your support URL. Upload approved PDF, TXT, Markdown or DOCX documents, up to 10 MB each, into your configured vector store. Indexing happens asynchronously; refresh the knowledge list until status is completed. Reindex is available for attached files.

You can also ingest a local approved document using cPanel Terminal:

```sh
php /home/CPANEL_USER/eeva-private/bin/ingest.php /home/CPANEL_USER/approved/company.md
```

No company facts or prices are invented when confirmed knowledge is unavailable. Replace example domains and supply your real approved documents before launch.

## 5. Paste the website snippet

Add this before the closing </body> tag, or in your website's custom HTML/footer area. Replace YOUR-DOMAIN with the assistant's host:

```html
<script src="https://YOUR-DOMAIN/eeva/embed.js" defer></script>
```

It adds a floating female presenter with a “Talk to me” button and opens the assistant. The small minus control minimizes the portrait. Use data-style="compact" on the script for a small button instead. The presenter is a still AI-generated portrait with speaking/listening indicators; it is not lip-synced video. To change the label:

```html
<script src="https://YOUR-DOMAIN/eeva/embed.js"
        data-label="Ask Eeva" defer></script>
```

For an inline assistant:

```html
<div id="eeva-assistant"></div>
<script src="https://YOUR-DOMAIN/eeva/embed.js"
        data-target="#eeva-assistant"
        data-height="720"
        defer></script>
```

Add the website's exact HTTPS origin to embed_origins in private config.php. Include both www and non-www if visitors use both. No trailing slash or page path in an origin.

The widget uses an iframe and isolated wrapper styles. It uses a signed in-memory session header, so it does not require third-party cookies. Closing the floating widget removes the iframe and stops its microphone/audio; reopening creates a new session.

If your website sends a restrictive Content-Security-Policy, allow the assistant origin in script-src, style-src, img-src and frame-src. For cross-origin microphone use, the parent website must permit the assistant, for example:

```http
Permissions-Policy: microphone=(self "https://YOUR-DOMAIN")
```

The iframe already requests microphone permission. Browser permission and HTTPS are still required. Never put your OpenAI key or admin token in a snippet.

## Voice on shared hosting

Recorded voice is the default: start the microphone, speak, then finish recording. PHP sends the recording for transcription, generates the answer and streams speech playback. Typed chat and replay are also supported. Raw recordings are not retained by this application.

Optional live WebRTC is available by setting live_enabled to true, provided your account supports the configured Live model. Ordinary cPanel hosting cannot run the original persistent server-side Live controller: this edition relies on browser-controlled session shutdown. max_voice_seconds is a browser timer, not a hard provider-enforced spending cap. Leave Live disabled unless that limitation is acceptable. Check billing limits in your provider account.

## Optional MariaDB / MySQL

File storage needs no database and suits modest traffic. For MySQL:

1. Create a database and user with cPanel Database Wizard.
2. Grant that user privileges on that database.
3. Import eeva-private/schema.sql in phpMyAdmin.
4. Set storage to mysql and fill mysql_dsn, mysql_user and mysql_password.
5. Enable pdo_mysql for the domain's PHP version.

Alternatively run bin/migrate.php using the matching PHP CLI version. This implementation stores state in one transactionally locked database row; it is intended for modest traffic, not large concurrent deployments. It does not migrate existing file-storage records.

## Retention and cleanup

retention_days defaults to 7; zero disables saved analytics/conversation records. Active conversation context expires after one hour of inactivity. Starting a new conversation deletes its current application records. OpenAI retention is controlled separately by your OpenAI account.

Expiry is applied on requests. Add an hourly cPanel cron job if you need cleanup during idle periods:

```sh
/usr/local/bin/php /home/CPANEL_USER/eeva-private/bin/cleanup.php
```

Your host may use a different PHP CLI path. Backups are managed separately by the hosting account.

## Troubleshooting

- **Setup required / HTTP 503:** verify the private path, PHP version/extensions, file permissions and config.php syntax. Check cPanel error logs.
- **AI setup pending:** set openai_api_key. A configured key is not proof that the account has model access.
- **Generic AI service error:** check account credit, model access, outbound network access and the configured model names.
- **403 Origin error:** app_url must match the browser's actual scheme, host and port. Use one canonical HTTPS domain.
- **Widget refused to connect:** check embed_origins, your site's frame-src policy and extra X-Frame-Options/CSP headers injected by the hosting provider.
- **Admin unauthorized:** use the exact 32+ character token and verify Authorization reaches PHP.
- **Replies arrive all at once:** ask your host to disable FastCGI/proxy buffering and compression for api.php. PHP cannot override every upstream proxy. .user.ini changes may take several minutes.
- **500 after upload:** some hosts prohibit Options in .htaccess. Ask the host or remove only the Options -Indexes line; retain the FilesMatch protection.
- **Microphone unavailable:** use HTTPS, allow browser microphone access and check the parent website's Permissions-Policy. Typed chat remains available.
- **429 Too many requests:** default limit is 30 public requests per IP/minute and 20 admin requests per IP/minute. Adjust rate_limit to your expected shared-network traffic.

## What was verified

Local PHP 8.5.10: syntax checks, 10 storage/configuration checks, 24 HTTP checks, and Chromium checks for chat rendering, session-header forwarding, responsive layouts, widget opening/closing and admin rendering. The streamed reply in the browser test is mocked. No production API key or hosting account was supplied, so actual AI/audio calls, MariaDB and your cPanel host still need a configured smoke test.

Official references: [cPanel MultiPHP Manager](https://docs.cpanel.net/cpanel/software/multiphp-manager-for-cpanel/), [OpenAI response streaming](https://developers.openai.com/api/docs/guides/streaming-responses).
