detain/phlix-server

Maintainers

Package info

github.com/detain/phlix-server

Type:project

pkg:composer/detain/phlix-server

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 4

Open Issues: 0


README

PHPUnit Coding Standards Admin UI codecov PHP PHPStan Code style

A comprehensive media server platform built with PHP 8.3+, featuring real-time WebSocket communication, HTTP REST APIs, and support for multiple client platforms including Roku, Samsung Tizen, and Windows.

Repository moved 2026-05-17: this codebase migrated from github.com/detain/phlix to github.com/detain/phlix-server as part of the Phase B repo split (see PHLIX_EXPANSION_PLAN.md). Update existing local clones with git remote set-url origin git@github.com:detain/phlix-server.git. The old repo is being archived in step B.4b.

Overview

Phlix Media Server provides a complete media management and streaming solution:

  • Media Library Management: Organize and browse media collections with automatic scanning
  • User Authentication: JWT-based auth with refresh tokens
  • Real-time SyncPlay: Watch content together with friends
  • Live TV Support: DVR and guide integration
  • DLNA Streaming: Standard protocol support for compatible devices
  • Transcoding: On-the-fly media conversion via FFmpeg with automatic quality selection
  • HLS Streaming: Adaptive bitrate streaming for web clients with multi-quality playlists
  • WebSocket Events: Real-time progress and notification delivery
  • Multi-Source Metadata: Automatic metadata fetching from TMDB (movies), TVDB (TV series), Fanart.tv (artwork), and local NFO files with 24-hour cache and provider fallback
  • Content Filtering: Parental controls with rating and genre-based filtering

Architecture

src/
├── Server/
│   ├── Core/           # Application bootstrap and core
│   ├── Http/            # HTTP REST API layer
│   │   ├── Controllers/ # Request handlers
│   │   ├── Request.php  # HTTP request representation
│   │   ├── Response.php # HTTP response builder
│   │   └── Router.php  # Route dispatching
│   ├── WebSocket/       # Real-time communication
│   │   ├── Connection.php      # Client connection wrapper
│   │   ├── ConnectionPool.php  # Connection management
│   │   ├── MessageHandler.php  # Event routing
│   │   ├── WebSocketServer.php # Server implementation
│   │   └── Events.php          # Event type constants
│   └── WebPortal/       # Web portal (HTML UI)
│       ├── WebPortalRouter.php # REST API for portal
│       └── PageRenderer.php    # Smarty template rendering
├── Session/            # Playback session management
├── Media/              # Media library and metadata
│   ├── Library/        # Library management (LibraryManager, ItemRepository, MediaScanner)
│   ├── Metadata/      # Metadata fetching (TMDB, TVDB, Fanart, NFO providers)
│   ├── Transcoding/    # FFmpeg transcoding with EncodingHelper
│   └── Streaming/      # HLS streaming with adaptive bitrate
├── Auth/               # Authentication services
└── Common/             # Shared utilities

public/
├── index.php           # Web portal entry point
├── templates/          # Smarty templates
└── assets/             # Static assets (css, js)

Requirements

  • PHP: 8.3 or higher
  • MySQL: 8.0+ or MariaDB 10.6+
  • Workerman: 5.0+ (bundled via Composer)
  • FFmpeg: For transcoding (optional)

Features

Foundation

  • PSR-11 DI container (PHP-DI 7): auto-wired services with provider-based composition; see phlix-docs / dev / architecture-server.
  • PSR-14 event dispatcher (Tukio): playback, library-scan, and auth lifecycle events with typed readonly DTOs. Plugins subscribe by event class FQCN; see phlix-docs / dev / event-reference.
  • Plugin system: install / enable / disable / uninstall lifecycle, sandboxed per-plugin vendor/ directories, signature-checked manifests, and PSR-14 event subscription via Phlix\Shared\Plugin\LifecycleInterface (the Phlix\Plugins\Contract\LifecycleInterface FQCN remains a deprecated bridge through 0.11.x). Plugin developer guide: phlix-docs / plugins / developer-guide. Server-internals reference for contributors extending the loader: phlix-docs / dev / plugin-sdk. Reference plugin: detain/phlix-plugin-example.
  • Shared interfaces / DTOs in detain/phlix-shared: framework-neutral Composer package shared with phlix-hub. Phlix\Shared\Plugin\*, Phlix\Shared\Events\*, Phlix\Shared\Auth\JwtClaims, and Phlix\Shared\Hub\* DTOs live there since phlix-server 0.11.0.

Web Portal

  • Smarty-based Templates: Server-side rendered HTML pages using Smarty
  • REST API Endpoints: Complete API for library browsing, media info, and user data
  • JWT Authentication: Integrated token-based auth with refresh support
  • Responsive Design: CSS-first approach with utility classes
  • JavaScript Client: ApiClient helper with auth, library, and player helpers
  • Continue Watching: Track and display in-progress media
  • Library Browser: Browse media by library with item counts

Authentication & Security

  • JWT-based Authentication: Stateless auth with access tokens (1 hour TTL) and refresh tokens (7 days TTL)
  • Secure Password Hashing: Argon2ID for password storage
  • Multi-Device Sessions: Track and manage sessions across devices
  • User Profiles: Multiple profiles per account with parental controls
    • Up to 5 profiles per user account
    • Profile-specific content rating restrictions (G, PG, PG-13, R, NC-17, X, UNRATED)
    • PIN protection (4 or 6 digits) for profile settings
    • Genre-based filtering (allowed/blocked genre lists)
    • Daily watch time limits per profile
  • Content Rating Filters: Age-based access restrictions
  • Audit Logging: Complete security event logging

SyncPlay - Group Watching

  • Synchronized Playback: Watch content together with friends across devices with sub-second sync accuracy
  • Host-Controlled Playback: Only the host can control play/pause/seek; all members receive synchronized commands
  • NTP-Style Time Sync: Network time synchronization with latency compensation and drift correction
  • In-Group Chat: Real-time messaging with typing indicators and message history
  • Playback Queue: Host-managed queue with media info (title, thumbnail)
  • Host Election: Automatic host election when current host leaves (oldest member becomes host)
  • Password Protection: Optional password protection for private watch parties
  • Position Tolerance: Configurable sync tolerance (default 2s) to prevent excessive seeking

Session Management

  • Device Sessions: Track authenticated devices with activity timestamps
  • Playback Progress: Resume where you left off across sessions
  • Continue Watching: Track items in progress per profile
  • Watch History: Complete viewing history per profile with:
    • Automatic completion detection at 90% progress threshold
    • Watch time statistics (total, daily, by period)
    • Resume position tracking for seamless playback continuation

Live TV & DVR

  • Multi-Tuner Support: DVB-T, DVB-S, DVB-C, and ATSC tuner types
  • Channel Scanning: Automatic discovery of broadcast services
  • Electronic Program Guide: Full EPG with program info, categories, and search
  • DVR Scheduling: Schedule recordings with priority management
  • Time-Shifting: Pause and rewind live TV with buffer
  • Channel Lineups: Custom channel lineups per user
  • Favorites: Personal favorite channels per user
  • Storage Management: Recording storage tracking and limits

Installation

One-line install (Ubuntu/Debian)

On a fresh Ubuntu/Debian host, scripts/install.sh does the whole thing: system packages (PHP 8.3+, MySQL, ffmpeg), a dedicated phlix system user, MySQL database + user, application code, env file at /etc/phlix/env, generated PHLIX_SECRET_KEY, database migrations, a systemd phlix-server service, and an HAProxy reverse proxy with an auto-renewing Let's Encrypt certificate.

The installer also compiles the Swoole + php-uv extensions from source (the coroutine runtime Workerman uses), idempotently skipping the build when they already load, and runs a disable_functions preflight — see Swoole & php-uv on Linux.

curl -fsSL https://raw.githubusercontent.com/detain/phlix-server/master/scripts/install.sh | sudo bash

Provision HTTPS in the same run by passing your domain and a Let's Encrypt contact email:

curl -fsSL https://raw.githubusercontent.com/detain/phlix-server/master/scripts/install.sh \
  | sudo bash -s -- --domain phlix.example.com --admin-email you@example.com

The script prompts for the install path, database user/password, and hostname when run in a terminal (with sensible defaults), and runs fully unattended when piped or given -y. Run sudo bash scripts/install.sh --help for every flag. Default ports: HTTP on :8096 behind HAProxy on :80/:443; DLNA discovery on 1900/udp.

Install flags

sudo bash scripts/install.sh --help lists every option. The most useful:

Flag Effect
--domain HOST Public hostname for the server (enables TLS when paired with --admin-email)
--admin-email EMAIL Email registered with Let's Encrypt
--db-name, --db-user, --db-pass, --db-host, --db-port MySQL identity (random password if --db-pass omitted). Note: config/database.php hardcodes host/port/db/user; only the password is env-driven.
--http-port PORT HTTP listen port (default 8096)
--tmdb-api-key KEY TMDB API key for metadata (optional, recorded in /etc/phlix/env)
--hub-url URL PHLIX_HUB_URL for hub relay (optional)
--service-user USER System user to run as (default phlix — dedicated system account, created if missing)
--branch NAME Git branch or tag to install (default master)
--repo URL Git repository URL (default detain/phlix-server)
--tls / --no-tls Force or skip Let's Encrypt + HAProxy TLS
--no-proxy Skip the managed HAProxy entirely (use your own reverse proxy)
--update Pull new code + run migrations on an existing install (preserves env + secrets)
--uninstall Remove the install — interactive prompts before each destructive step
--purge With --uninstall, also drop the DB, delete the Let's Encrypt cert, wipe /var/phlix, and remove the dedicated system user
-y, --non-interactive Never prompt; use defaults/flags
--interactive Force prompts even when piped

Updating an existing install

The same scripts/install.sh updates an in-place install without rotating any secrets. It reads the existing /etc/phlix/env (so DB_PASSWORD and PHLIX_SECRET_KEY are preserved), pulls the latest code, refreshes Composer dependencies, runs migrations, and restarts the service:

sudo bash /var/www/phlix/scripts/install.sh --update -y

Pin to a specific tag or branch with --branch:

sudo bash /var/www/phlix/scripts/install.sh --update --branch v0.2.0 -y

--update discovers the install path from the systemd unit's WorkingDirectory, fetches code as the install dir owner (so it doesn't trip Git's CVE-2022-24765 dubious-ownership check), runs composer install --no-dev --optimize-autoloader, clears templates_c/, runs scripts/run-migrations.php, restarts phlix-server, and curl-checks /health. It deliberately leaves the env file, MySQL grants, HAProxy config, and Let's Encrypt cert alone.

Uninstalling

scripts/install.sh --uninstall removes an existing install. It is interactive by default and prompts separately before each destructive step. The MySQL database, the /var/phlix data directory, and the Let's Encrypt certificate are kept unless you opt in:

sudo bash /var/www/phlix/scripts/install.sh --uninstall

Add --purge to also drop the database (and user), wipe /var/phlix (config, library cache, backups), and delete the Let's Encrypt certificate via certbot delete. Combine with -y for a fully unattended teardown:

sudo bash /var/www/phlix/scripts/install.sh --uninstall --purge -y

What it removes when present:

  1. The phlix-server systemd unit (stop, disable, remove file, daemon-reload).
  2. HAProxy fragment at /etc/haproxy/phlix-managed/phlix-server.cfg.fragment, and /etc/haproxy/haproxy.cfg is rebuilt. If phlix-hub is still installed, its frontend + backend stay. If phlix-server was the last Phlix project, the pre-Phlix snapshot at /etc/haproxy/haproxy.cfg.pre-phlix.bak is restored (or haproxy.cfg is removed and haproxy is stopped + disabled if no snapshot exists).
  3. The combined PEM at /etc/haproxy/certs/<domain>.pem.
  4. /etc/cron.d/phlix-server-certbot and the certbot deploy hook.
  5. The Let's Encrypt cert via certbot delete — only with --purge or interactive confirm.
  6. The MySQL database + user — only with --purge or interactive confirm.
  7. The install dir (/var/www/phlix by default; system paths refused).
  8. /var/phlix (config, library cache, backups) — only with --purge or interactive confirm.
  9. /var/log/phlix and /var/run/phlix.
  10. /etc/phlix/env (env file).
  11. The dedicated system user phlix via userdel — only with --purge or interactive confirm. Refuses to touch shared OS accounts (www-data, root, etc.). Cross-detects phlix-hub's systemd unit and refuses to remove a user that's still being used by it.

System packages (php-*, mysql-server, ffmpeg, haproxy, certbot) and ufw rules are left in place — sudo apt remove … / sudo ufw delete … to remove them.

Running alongside phlix-hub on the same server

Both installers can share a single HAProxy instance — they auto-merge into one /etc/haproxy/haproxy.cfg. Just run both installers normally; the second one detects the first's fragment and rebuilds a combined config that routes by Host: header.

# 1. Install phlix-hub first (with TLS).
curl -fsSL https://raw.githubusercontent.com/detain/phlix-hub/master/scripts/install.sh \
  | sudo bash -s -- --domain hub.example.com --admin-email you@example.com -y

# 2. Install phlix-server, also with TLS, on a different hostname.
curl -fsSL https://raw.githubusercontent.com/detain/phlix-server/master/scripts/install.sh \
  | sudo bash -s -- --domain phlix.example.com --admin-email you@example.com -y

After both finish, /etc/haproxy/haproxy.cfg looks like:

# phlix-managed: rebuilt by phlix install scripts — do not edit
...
frontend fe_https
    bind :443 ssl crt /etc/haproxy/certs/
    http-request set-header X-Forwarded-Proto https

    # --- phlix-hub ---
    acl is_phlix_hub_host hdr(host) -i hub.example.com
    use_backend be_hub_client_relay if is_phlix_hub_host { path_beg /client/ }
    use_backend be_hub if is_phlix_hub_host

    # --- phlix-server ---
    acl is_phlix_server_host hdr(host) -i phlix.example.com
    use_backend be_phlix_server if is_phlix_server_host
    ...

How the merge works. Each install drops a fragment at /etc/haproxy/phlix-managed/<project>.cfg.fragment with fe_http, fe_https, and backends sections. A rebuilder function then assembles the final haproxy.cfg from every fragment it finds. HAProxy's crt /etc/haproxy/certs/ directive auto-loads every .pem in that directory and picks the right one per SNI hostname.

The first install snapshots any pre-Phlix haproxy.cfg to /etc/haproxy/haproxy.cfg.pre-phlix.bak.

Uninstall behaviour: --uninstall removes only that project's fragment and rebuilds. If other Phlix projects remain, their frontend stays untouched. When the last Phlix project is uninstalled, the rebuilder restores the pre-Phlix snapshot (or removes haproxy.cfg outright if there was no pre-Phlix config) and stops/disables haproxy.

The hub server-tunnel port (:8802) is a separate listener — servers connect to that port directly. Open it on the firewall but don't put it behind the HAProxy 80/443 frontend.

If you'd rather use your own reverse proxy (nginx, Caddy, Traefik, etc.) instead of the managed HAProxy, pass --no-proxy to either install script. Each service then listens on its own port (8096 for phlix-server, 8800 for phlix-hub) and you point your proxy at those.

Everything else is already namespaced: env files (/etc/phlix-hub.env vs /etc/phlix/env), systemd units (phlix-hub.service vs phlix-server.service), install dirs (/opt/phlix-hub vs /var/www/phlix), service users (www-data vs phlix), MySQL DBs (phlix_hub vs phlix), backend ports (8800/8802/8803 vs 8096), and certbot artefacts.

Manual install (from source)

# Clone the repository
git clone https://github.com/detain/phlix-server.git
cd phlix-server

# Install dependencies
composer install

# Run database migrations (reads config/database.php; password from DB_PASSWORD env var)
DB_PASSWORD=your_strong_password php bin/phlix migrate    # or: php scripts/run-migrations.php

# Start the server (HTTP + WebSocket on port 8096 from config/server.php)
php public/index.php start

Configuration

Configuration is managed via PHP files in config/:

// config/server.php
return [
    'server' => [
        'name' => 'Phlix Media Server',
        'host' => '0.0.0.0',
        'port' => 8080,
    ],
    'websocket' => [
        'host' => '0.0.0.0',
        'port' => 8097,
    ],
    'database' => [
        'host' => '127.0.0.1',
        'port' => 3306,
        'database' => 'phlix',
        'username' => 'phlix',
        'password' => 'secure-password',
    ],
    'debug' => false,
];

API Reference

HTTP Endpoints

Method Path Description
GET /health Health check
GET /system/info Server information
POST /api/v1/auth/register User registration
POST /api/v1/auth/login User login
POST /api/v1/auth/refresh Token refresh
GET /api/v1/auth/me Current user profile
GET /api/v1/sessions List user sessions
DELETE /api/v1/sessions/{id} End a session
POST /api/v1/sessions/{id}/progress Report playback progress
GET /api/v1/sessions/{id}/progress Get playback state
GET /api/v1/admin/settings Effective server settings (config default + DB override) — admin-only
PUT /api/v1/admin/settings Persist server-setting overrides — admin-only
GET /api/v1/admin/fs/browse List subdirectories under allowed roots (library path picker) — admin-only
GET /api/v1/admin/users List all users — admin-only
GET /api/v1/admin/users/{id} Get a specific user — admin-only
POST /api/v1/admin/users Create a new user — admin-only
PUT /api/v1/admin/users/{id} Update a user (username, email, password) — admin-only
DELETE /api/v1/admin/users/{id} Delete a user — admin-only
POST /api/v1/admin/users/{id}/set-admin Promote or demote a user — admin-only
POST /api/v1/admin/users/{id}/reset-password Reset a user's password (returns new password) — admin-only

WebSocket Events

Connection Events:

  • connected - Sent on successful connection
  • client_disconnected - Broadcast when client disconnects

Authentication Events:

  • auth_request - Request authentication
  • auth_success - Authentication successful
  • auth_failure - Authentication failed

Playback Events:

  • playback_start - Playback started
  • playback_pause - Playback paused
  • playback_stop - Playback stopped
  • playback_progress - Progress update
  • playback_seek - Seek performed

SyncPlay Events:

  • syncplay_create_group - Create watch group
  • syncplay_join_group - Join watch group
  • syncplay_leave_group - Leave watch group
  • syncplay_sync_state - State synchronization

Development

Running Tests

# Run all tests
./vendor/bin/phpunit

# Run with coverage
./vendor/bin/phpunit --coverage-html coverage-report

# Run specific test suite
./vendor/bin/phpunit --testsuite Unit
./vendor/bin/phpunit --testsuite Integration

Code Standards

This project follows PSR-12 coding standards and uses static analysis tools:

# Check code style
./vendor/bin/phpcs --standard=PSR12 src/

# Run static analysis
./vendor/bin/phpstan analyze src/ --level=9
./vendor/bin/psalm

CLI

Administrative tasks are exposed through the bin/phlix command-line tool (built on webman/console / Symfony Console):

php bin/phlix list       # list every available command
php bin/phlix migrate    # apply migrations/*.sql against config/database.php

php bin/phlix migrate is the supported equivalent of php scripts/run-migrations.php — both delegate to the same Phlix\Common\Database\MigrationRunner service, applying every migrations/*.sql file on each run (idempotent; no tracking table). More commands are added in later steps.

Admin SPA (admin-ui)

The admin console is a React + TypeScript + Vite single-page app. Its source lives in admin-ui/; the production build is emitted into public/assets/admin/ and committed to the repo, so the running Workerman server has no Node build dependency at runtime (it just serves the static shell + bundle). admin-ui/node_modules/ is gitignored.

The SPA mounts at /admin and /admin/*, served by AdminAppController (returns the built index.html shell; 503 if the bundle is missing) and gated by the existing AdminMiddleware — a non-admin (401/403) is redirected (302) to /login. Its typed ApiClient reuses the same JWT mechanism as public/assets/js/api-client.js (access_token/refresh_token in localStorage, Bearer header, single retry on 401 via POST /auth/refresh).

cd admin-ui
npm install          # one-time / on dependency changes
npm run build        # tsc + vite build → ../public/assets/admin/ (commit the result)
npm run test         # Vitest unit/component tests
npm run dev          # Vite dev server (HMR) for local development

When you change anything under admin-ui/src/, re-run npm run build and commit the refreshed public/assets/admin/ bundle along with your source changes.

CI runs the SPA build + Vitest suite on any change under admin-ui/ via the Admin UI GitHub Actions workflow (.github/workflows/admin-ui.yml): npm ci → npm run build → npm run test on push/PR to master/main/develop (path-filtered, so PHP-only changes don't trigger it).

The first feature page on top of the scaffold is the Libraries page at /admin/libraries (step 1.1c): list / add / edit / delete libraries, a PathPicker driving the 0.6 GET /api/v1/admin/fs/browse endpoint, per-row Scan / Rescan buttons that hit the async 1.1b scan API (POST /api/v1/libraries/{id}/scan|rescan → 202 {job_id, status: "queued"}), live coarse lifecycle status by polling GET .../scan-status every 2 s (polling stops on completed / failed), and a per-library scan-history modal. No backend changes — the page consumes only contracts already shipped by 0.6 and 1.1b.

The Settings page at /admin/settings (step 1.3) renders all 15 server-setting keys across 8 tabbed groups (Transcoding, Metadata, Markers, Subtitles, Discovery, Trickplay, Newsletter, Port Forward). It consumes the 0.5 GET/PUT /api/v1/admin/settings contract — no new endpoints were added. Bool keys render as toggle switches; numeric keys render as number inputs with min/max constraints; tmdb.api_key renders as a password field with Show/Hide toggle. Overridden keys (DB-persisted vs. config-file default) display a "custom" badge. A sticky Save button fires PUT /api/v1/admin/settings; dirty-state gating keeps the button disabled when no fields have changed.

The Webhooks page at /admin/webhooks (step 1.4a) provides full CRUD for webhook subscriptions plus a per-webhook test trigger. It consumes the five endpoint contract in WebhookAdminController (GET list, POST create, PUT update, DELETE remove, POST test). The PUT /api/v1/admin/webhooks/{id} route, plus the backing WebhookDispatcher::update() and WebhookAdminController::update() PHP methods, were added in this step to support edit-in-place (the controller already had index/create/delete/test before 1.4a). The event multi-select shows 7 subscribable events grouped into 5 categories (Playback, Library, Downloads, Recordings, Alerts); webhook.test is internal-only. Secret is write-only — GET never returns it; the edit form shows an empty field with "(unchanged)" placeholder and omits secret from the PUT payload when blank, so the server retains the stored value. Coverage: 97.29% on webhooks.ts, 89.74% on WebhooksPage.tsx.

Full operator + contributor docs live in the phlix-docs site (docs/admin/integrations.md, docs/admin/server-settings.md, and docs/dev/admin-spa.md).

Git Workflow

  1. Create a feature branch: git checkout -b feature/my-feature
  2. Make changes and commit: git commit -am 'Add new feature'
  3. Push to remote: git push origin feature/my-feature
  4. Create Pull Request on GitHub
  5. After review, merge via squash-merge

Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Ensure all tests pass (./vendor/bin/phpunit)
  4. Follow PSR-12 coding standards
  5. Submit a pull request

License

Proprietary - All rights reserved.

Support

For issues and feature requests, please use the GitHub issue tracker.

For detailed development documentation, see DEVELOPER.md.