CLI Reference β
The Phlix Media Server and the Phlix Hub each ship a bin/phlix console entrypoint built on webman/console (a thin wrapper around Symfony Console). A handful of operations that are network-, daemon-, or TLS-bound are still provided as standalone scripts/*.php and are documented at the end of this page.
bin/phlix β what it is β
webman/console only auto-discovers commands from an app/command directory, which neither repo has (both use a PSR-4 Phlix\β¦ β src/ layout). So each repo ships a small bin/phlix executable that bootstraps the autoloader and config, then explicitly registers its Phlix\β¦\Console\Commands\* classes on a Webman\Console\Command application and runs it.
bin/phlix is a one-shot CLI β not the resident Workerman/Swoole worker β so it does not start the event loop. The DI container and the database connection are resolved lazily: a command only builds the container or opens a connection when it actually needs one. Consequently:
php bin/phlix list # list every available command (no database needed)
php bin/phlix help <command>php bin/phlix list works with no database configured or reachable β it never builds the container, so you can always discover the available commands.
All commands return a standard exit code: 0 on success, 1 on failure (the error is written to output). Commands never call exit()/die() internally.
phlix-server commands β
Run from the phlix-server install directory (phlix-server/). Twelve commands are available.
| Command | Arguments / options | Description |
|---|---|---|
migrate | β | Apply database migrations (migrations/*.sql). |
library:list | β | List all configured media libraries. |
library:scan | {libraryId} [--rescan] | Scan (or rescan) a media library for new content. |
plugin:list | β | List installed plugins and their enabled state. |
plugin:enable | {name} | Enable an installed plugin by name. |
plugin:disable | {name} | Disable an enabled plugin by name. |
plugin:install | {source} | Install a plugin from a source URL. |
plugin:uninstall | {name} | Uninstall a plugin by name. |
backup:create | [--label=] | Create a new server backup archive. |
backup:list | β | List stored server backups. |
hwaccel:probe | β | Probe for available hardware-acceleration encoders. |
user:reset-password | {user} [--password=] | Reset a user's password by username or email. |
migrate β
Applies every migrations/*.sql file in sorted order. Idempotent: it has no migration-tracking table and is safe to run repeatedly β duplicate-column / duplicate-key / "already exists" errors are downgraded to notes rather than treated as failures. This is the supported equivalent of php scripts/run-migrations.php (both delegate to the same Phlix\Common\Database\MigrationRunner); the script remains for the Docker entrypoint and installer.
php bin/phlix migrateReturns exit 1 if a genuine (non-idempotent) statement error occurs.
library:list β
Prints the id, name, type, and configured path(s) of each library as a table.
php bin/phlix library:listlibrary:scan β
Scans a library for new content. Pass --rescan to clear existing items and rescan from the filesystem.
This command runs synchronously and blocks until the scan completes. (The HTTP POST /api/v1/libraries/{id}/scan endpoint is asynchronous instead β it queues a job; see the Library Scan Worker.)
| Argument / option | Description |
|---|---|
libraryId (required) | The library identifier to scan. |
--rescan | Clear existing items and rescan from scratch. |
php bin/phlix library:scan 3
php bin/phlix library:scan 3 --rescanplugin:list β
Lists installed plugins with their version and enabled state (yes/no) as a table.
php bin/phlix plugin:listplugin:enable / plugin:disable / plugin:uninstall β
Each takes a required name argument β the plugin's manifest name.
php bin/phlix plugin:enable my-plugin
php bin/phlix plugin:disable my-plugin
php bin/phlix plugin:uninstall my-pluginplugin:install β
Installs a plugin from a source. The source argument is required and accepts an HTTPS URL or a file:// path for local sources. (Plain http:// is rejected unless PHLIX_PLUGINS_ALLOW_HTTP is enabled β see Environment variables.)
| Argument | Description |
|---|---|
source (required) | The plugin source URL (HTTPS, or file:// for local sources). |
php bin/phlix plugin:install https://plugins.example.com/my-plugin.zipOn success it prints the installed plugin's name and version.
backup:create β
Creates a new server backup archive and prints the backup id, file path, and size.
| Option | Description |
|---|---|
--label= | Optional human-readable label for the backup. |
php bin/phlix backup:create
php bin/phlix backup:create --label="before 1.2 upgrade"backup:list β
Lists stored backups (id, label, size, location β local or S3 β and creation time) as a table.
php bin/phlix backup:listhwaccel:probe β
Probes for available hardware-acceleration encoders/decoders using the configured ffmpeg binary (from config/ffmpeg.php) and renders the detected vendor, encoder, decoder, HDR support, and codecs per capability. Needs neither the container nor a database.
php bin/phlix hwaccel:probeuser:reset-password β
Resets a user's password, looking the user up by username first, then by email. The password is hashed with Argon2ID before storage.
| Argument / option | Description |
|---|---|
user (required) | The username or email of the user to reset. |
--password= | The new password. When omitted, a strong random password is generated and printed to stdout. |
# Generate and print a strong random password
php bin/phlix user:reset-password alice@example.com
# Set a specific password (not echoed back)
php bin/phlix user:reset-password alice --password='S3cret!Passphrase'Returns exit 1 if the user is not found.
phlix-hub commands β
Run from the phlix-hub install directory (phlix-hub/). Two commands are available.
| Command | Arguments / options | Description |
|---|---|---|
migrate | β | Apply database migrations (migrations/*.sql). |
smoke:jwt | β | Smoke-test the JWT create/validate round-trip. |
migrate β
Applies the hub's pending migrations/*.sql. Unlike the server's migrate, the hub uses a real migration-tracking table (Phlix\Hub\Common\Database\MigrationRunner), so already-applied migrations are skipped. It is the supported equivalent of php scripts/run-migrations.php.
php bin/phlix migrateOutput reports each newly applied file and the total, or "All migrations already applied. Nothing to do." when up to date.
smoke:jwt β
Runs a self-contained JWT create-then-validate round-trip using a throwaway test secret (no config or database). Useful for verifying the JwtHandler β JwtClaims wiring after a deploy. Prints OK: JWT round-trip succeeded and the asserted claim fields on success; returns exit 1 on any mismatch.
php bin/phlix smoke:jwtStandalone scripts (not yet bin/phlix commands) β
The following operations remain scripts/*.php rather than bin/phlix commands. They are network-, daemon-, or TLS-bound and are deferred to a later phase; there is no bin/phlix equivalent for them today.
php scripts/pair-with-hub.php <hub-url> <server-name> β
Initiates pairing between this server and a Phlix Hub instance.
Arguments:
| Argument | Description |
|---|---|
hub-url | Base URL of the hub (e.g. https://hub.example.com). |
server-name | Human-readable name shown on the hub dashboard. |
Example:
php scripts/pair-with-hub.php https://hub.example.com "Alice's NAS"Output:
Pairing initiated.
Claim code: ABCD-1234
Enter this code at https://hub.example.com/claim-server
Waiting for claim... (press Ctrl+C to cancel)
Claimed! Server ID: 550e8400-e29b-41d4-a716-446655440000
Enrollment stored.
Pairing complete. Server is now connected to the hub.
Heartbeat loop has been started in the background.Behavior:
- Generates (or loads existing) Ed25519 keypair from
config/hub-server-key.pem. - Sends a claim request to
POST <hub-url>/api/v1/server-claims/new. - Displays the returned claim code for the operator to enter on the hub's web portal.
- Polls
GET <hub-url>/api/v1/server-claims/{claimId}every 2 seconds. - On successful claim, stores enrollment JWT to
config/hub-enrollment.json. - Starts the background heartbeat loop.
Exit codes:
0β Pairing completed successfully.1β Error (network failure, invalid arguments, hub rejection).
See Phlix\Hub\HubClient and docs/dev/pairing-protocol.md.
php scripts/claim-subdomain.php β
Claims a *.phlix.media subdomain for the enrolled server after pairing. Note: automated TLS provisioning is not implemented β certificates must be provisioned out-of-band (see TLS Certificates).
php scripts/port-forward.php <command> β
Manages UPnP-IGD and NAT-PMP port forwarding for direct server access without a relay tunnel.
Commands:
| Command | Description |
|---|---|
status | Show current port forwarding status, enabled state, method, and hostname candidates. |
enable | Attempt automatic port forwarding via UPnP-IGD or NAT-PMP. Falls back to manual instructions on failure. |
disable | Remove all port mappings and disable automatic port forwarding. |
info | Display detailed network information: local IP, public IP (via STUN), port accessibility, and UPnP IGD discovery status. |
help | Show usage information. |
Example output (status):
Port Forwarding Status
=======================
Enabled: YES
Method: upnp
External IP: 203.0.113.42
Port: 32400
Endpoint: 203.0.113.42:32400
Hostname Candidates:
[lan] http://192.168.1.100:32400
[lan-mdns] http://phlix.local:32400
[public] http://203.0.113.42:32400Example output (info):
Network Information
====================
Local IP: 192.168.1.100
Port: 32400
Testing STUN (public IP detection)...
Public IP: 203.0.113.42
Port 32400 on 203.0.113.42: OPEN
UPnP IGD Discovery...
Gateway: http://192.168.1.1:1900/gateway.xml
External WAN IP: 203.0.113.42How it works:
- UPnP-IGD β Sends SSDP M-SEARCH to
239.255.255.250:1900to discover a UPnP InternetGatewayDevice, then uses SOAPAddPortMappingto open the port. SeePhlix\Network\UpnpIgdClient. - NAT-PMP β Falls back to Apple NAT-PMP (RFC 6886) on routers like AirPort Extreme. See
Phlix\Network\NatPmpClient. - STUN β Uses RFC 5389 STUN binding to discover the server's public IP address and test port accessibility. See
Phlix\Network\StunClient.
See also: docs/hub/remote-access.md.
php scripts/run-marker-detection-worker.php β
Intro/outro marker-detection background worker (an infinite daemon loop). This script is a carry-over and is not wrapped as a bin/phlix command β it belongs to a future queue/worker step.
php scripts/dedup-series.php [--library=ID] [--dry-run|--apply] β
Find and (optionally) merge duplicate top-level items (series and movies) across the catalog. Title-slug variance β separators, year bleed, a flatβper-directory re-scan, or a concurrent-scan race β can create a second top-level row for the same show or film (the classic "100 episodes + 1 episode" symptom). This script is the offline counterpart of the admin Duplicates page / POST /api/v1/admin/media/merge endpoint: it runs DuplicateFinder per library and, on --apply, collapses each group with SeriesMerger.
Options:
| Option | Description |
|---|---|
--library=ID | Restrict to a single library UUID. Omit to process every library. |
--dry-run | List the duplicate groups that would be merged, without mutating anything. This is the default. |
--apply | Actually merge each group (re-parent children onto the primary, delete empty shells / duplicate movie rows). |
Behavior:
- Default mode is dry-run β you must pass
--applyto make changes. - The "primary" of each group is the member with the most descendants; the rest are re-parented into it. Re-parented episodes keep their ids, so per-user playback progress survives; only empty shells and duplicate movie rows are deleted (their own per-user rows go via
ON DELETE CASCADE). - A re-run after
--applyreports zero groups (idempotent).
Prerequisite: migration 043_media_items_canonical_key.sql (adds a nullable, non-unique canonical_key column + a (library_id, type, canonical_key) index). There is intentionally no UNIQUE constraint β historical duplicates exist; uniqueness is enforced in application code at scan time.
Example:
# Preview duplicate groups in one library (no changes)
php scripts/dedup-series.php --library=550e8400-e29b-41d4-a716-446655440001 --dry-run
# Merge duplicates across every library
php scripts/dedup-series.php --apply