Skip to content

detain/phlix-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

498 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Phlix Media Server

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.

About

Self-hosted media server in PHP 8 / Workerman. HLS+DASH, hardware transcoding, live TV, SyncPlay, plugins. A Plex/Jellyfin alternative.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors