Skip to content
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.txt

Every 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.

SQL
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 0

status 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).

data/items.lua
['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.

server.cfg
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_duo
server.cfg
set 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_duo
server.cfg
set 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_duo
server.cfg
set 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_duo

duo_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