Eureka Phone: Duo
Installation
From the release archive to a running phone: database, migrations, resources, server.cfg and the first start.
The release archive is eureka_duo-<version>-<commit>.zip. It holds both resources, the migration tool and the runbooks. Commands below use MariaDB tool names; on MySQL use mysql and mysqldump. Run them in a bash shell (Linux, or WSL on a Windows host).
1. Check the archive
sha256sum -c eureka_duo-<version>-<commit>.zip.sha256
unzip eureka_duo-<version>-<commit>.zip
cd eureka_duo-<version>-<commit>
sha256sum -c SHA256SUMS
cat RELEASE.txtEvery line must print OK. RELEASE.txt names the commits the archive was built from; keep it with your change record. The rest of this page runs in this folder.
2. Database and accounts
Create one database and two accounts: a migrator that applies schema changes and takes backups, and a runtime account for FXServer that cannot change the schema. Replace every <...> value and keep the passwords in a secret manager.
CREATE DATABASE eureka_duo CHARACTER SET utf8mb4;
-- Applies migrations and takes backups. Not used by FXServer.
CREATE USER 'duo_migrator'@'<admin-host>' IDENTIFIED BY '<migrator-password>';
GRANT CREATE, ALTER, DROP, INDEX, REFERENCES, SELECT, INSERT, UPDATE, DELETE
ON eureka_duo.* TO 'duo_migrator'@'<admin-host>';
-- Used by oxmysql at runtime. Cannot change the schema.
CREATE USER 'duo_runtime'@'<fxserver-host>' IDENTIFIED BY '<runtime-password>';
GRANT SELECT, INSERT, UPDATE, DELETE ON eureka_duo.* TO 'duo_runtime'@'<fxserver-host>';The server time zone must be UTC: set default-time-zone = '+00:00' under [mysqld] (or [mariadbd]), restart, and check with SELECT @@global.time_zone;. Otherwise the phone logs DB_TIMEZONE_NOT_UTC at start.
3. Schema
The migration tool reads its connection URL only from an environment variable, so the password stays out of arguments and shell history. Percent-encode special characters in the password.
npm ci --omit=dev --prefix tools/migrate
read -rs DUO_MIGRATION_DATABASE_URL && export DUO_MIGRATION_DATABASE_URL
# paste: mysql://duo_migrator:<migrator-password>@<db-host>:3306/eureka_duo
node tools/migrate/cli.mjs status # every migration pending
node tools/migrate/cli.mjs up
node tools/migrate/cli.mjs status # every migration applied, exit status 0status changes nothing and prints one line per migration: applied, pending or partial. If up stops partway, the phone refuses to start on the partial schema (SCHEMA_PARTIAL); the archive's docs/runbooks/database-migrations.md explains how to resume.
4. Resources
Copy resource/eureka_phone and resource/eureka_duo into your server's resources folder. Keep both folder names: every phone page names the core eureka_phone in its manifest, and the page's origin (https://cfx-nui-eureka_duo) is what your object store's CORS rule allows.
Already running another Eureka phone? Your server already has a core. Keep one eureka_phone folder, the newer of the two releases, and add only this archive's eureka_duo next to it.
5. Inventory item
Define the Duo's item in your inventory with stack = false (a stacked slot is ignored). The default item name is phone; it must appear in duo_phone_items and in the page's models (see Server settings).
['phone'] = {
label = 'Phone',
weight = 190,
stack = false,
close = true,
},6. server.cfg
Put the phone's settings in the environment-specific server.cfg (or txAdmin recipe) that git does not track. Set every value with set: never setr (clients could read it) or sets (it is published). Start order: framework and inventory, oxmysql, pma-voice, then the core, then its pages.
set mysql_connection_string "mysql://duo_runtime:<runtime-password>@<db-host>:3306/eureka_duo"
set duo_framework "qbox"
set duo_inventory "ox_inventory"
set duo_phone_items "phone"
ensure qbx_core
ensure ox_inventory
ensure oxmysql
ensure pma-voice
ensure eureka_phone
ensure eureka_duoset mysql_connection_string "mysql://duo_runtime:<runtime-password>@<db-host>:3306/eureka_duo"
set duo_framework "qbcore"
set duo_inventory "ox_inventory"
set duo_phone_items "phone"
ensure qb-core
ensure ox_inventory
ensure oxmysql
ensure pma-voice
ensure eureka_phone
ensure eureka_duoset mysql_connection_string "mysql://duo_runtime:<runtime-password>@<db-host>:3306/eureka_duo"
set duo_framework "esx"
set duo_inventory "ox_inventory"
set duo_phone_items "phone"
ensure es_extended
ensure ox_inventory
ensure oxmysql
ensure pma-voice
ensure eureka_phone
ensure eureka_duoset mysql_connection_string "mysql://duo_runtime:<runtime-password>@<db-host>:3306/eureka_duo"
set duo_framework "standalone"
set duo_inventory "ox_inventory"
set duo_phone_items "phone"
ensure ox_inventory
ensure oxmysql
ensure pma-voice
ensure eureka_phone
ensure eureka_duoduo_framework and duo_inventory have no defaults on purpose: a wrong guess could show one character's data to another.
7. First start
On a healthy start the console shows the schema check, a voice line (VOICE_AVAILABLE, or a warning when pma-voice is absent), then:
[eureka_phone] info READY: messaging available, calls available, media off, ...What else you may see:
| Console line | Meaning |
|---|---|
CONFIG_INVALID ... then DISABLED |
One line per invalid setting, naming it. Fix the value and restart. |
SCHEMA_* |
The database does not match this release: go back to step 3. |
ADAPTER_UNVERIFIED then DISABLED |
Your framework or inventory version has not passed the live adapter smoke test. On staging, set duo_adapter_verification "warn" turns this into a warning for that test. |
VOICE_RESOURCE_NOT_STARTED |
pma-voice is not running; calls are unavailable, messaging works. |
8. Smoke test before players
Run the release smoke test from the archive (docs/runbooks/release-smoke-test.md) on staging: open the phone with the key (F1), fold it (F2), send a message between two clients, place a call, and check the apps you enabled. Only then move the same files and settings to production.
Restarting the core stops its pages. FXServer stops eureka_duo together with eureka_phone and does not start it again on restart eureka_phone: always follow with ensure eureka_duo (and each other phone page).
Last updated October 2, 2026