{"id":51118881,"url":"https://github.com/detain/phlix-server","last_synced_at":"2026-06-25T00:30:25.812Z","repository":{"id":358407919,"uuid":"1241017641","full_name":"detain/phlix-server","owner":"detain","description":"Self-hosted media server in PHP 8 / Workerman. HLS+DASH, hardware transcoding, live TV, SyncPlay, plugins. A Plex/Jellyfin alternative.","archived":false,"fork":false,"pushed_at":"2026-06-20T23:12:14.000Z","size":13645,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-20T23:19:06.772Z","etag":null,"topics":["dlna","dvr","emby","ffmpeg","hls","home-theater","jellyfin","live-tv","media-library","media-server","php","php8","plex","self-hosted","streaming","syncplay","transcoding","video-streaming","workerman"],"latest_commit_sha":null,"homepage":"https://phlex.media","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/detain.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-16T21:26:42.000Z","updated_at":"2026-06-20T23:12:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/detain/phlix-server","commit_stats":null,"previous_names":["detain/phlex-server","detain/phlix-server"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/detain/phlix-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/detain%2Fphlix-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/detain%2Fphlix-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/detain%2Fphlix-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/detain%2Fphlix-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/detain","download_url":"https://codeload.github.com/detain/phlix-server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/detain%2Fphlix-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34755061,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dlna","dvr","emby","ffmpeg","hls","home-theater","jellyfin","live-tv","media-library","media-server","php","php8","plex","self-hosted","streaming","syncplay","transcoding","video-streaming","workerman"],"created_at":"2026-06-25T00:30:25.304Z","updated_at":"2026-06-25T00:30:25.797Z","avatar_url":"https://github.com/detain.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Phlix Media Server\n\n[![PHPUnit](https://github.com/detain/phlix-server/actions/workflows/phpunit.yml/badge.svg)](https://github.com/detain/phlix-server/actions/workflows/phpunit.yml)\n[![Coding Standards](https://github.com/detain/phlix-server/actions/workflows/coding-standards.yml/badge.svg)](https://github.com/detain/phlix-server/actions/workflows/coding-standards.yml)\n[![Admin UI](https://github.com/detain/phlix-server/actions/workflows/admin-ui.yml/badge.svg)](https://github.com/detain/phlix-server/actions/workflows/admin-ui.yml)\n[![codecov](https://codecov.io/gh/detain/phlix-server/graph/badge.svg)](https://codecov.io/gh/detain/phlix-server)\n[![PHP](https://img.shields.io/badge/PHP-8.3%2B-777bb4?logo=php\u0026logoColor=white)](https://www.php.net/)\n[![PHPStan](https://img.shields.io/badge/PHPStan-level%209-brightgreen)](https://phpstan.org/)\n[![Code style](https://img.shields.io/badge/code%20style-PSR--12-blueviolet)](https://www.php-fig.org/psr/psr-12/)\n\nA 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.\n\n\u003e **Repository moved 2026-05-17:** this codebase migrated from `github.com/detain/phlix` to [`github.com/detain/phlix-server`](https://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.\n\n## Overview\n\nPhlix Media Server provides a complete media management and streaming solution:\n\n- **Media Library Management**: Organize and browse media collections with automatic scanning\n- **User Authentication**: JWT-based auth with refresh tokens\n- **Real-time SyncPlay**: Watch content together with friends\n- **Live TV Support**: DVR and guide integration\n- **DLNA Streaming**: Standard protocol support for compatible devices\n- **Transcoding**: On-the-fly media conversion via FFmpeg with automatic quality selection\n- **HLS Streaming**: Adaptive bitrate streaming for web clients with multi-quality playlists\n- **WebSocket Events**: Real-time progress and notification delivery\n- **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\n- **Content Filtering**: Parental controls with rating and genre-based filtering\n\n## Architecture\n\n```\nsrc/\n├── Server/\n│   ├── Core/           # Application bootstrap and core\n│   ├── Http/            # HTTP REST API layer\n│   │   ├── Controllers/ # Request handlers\n│   │   ├── Request.php  # HTTP request representation\n│   │   ├── Response.php # HTTP response builder\n│   │   └── Router.php  # Route dispatching\n│   ├── WebSocket/       # Real-time communication\n│   │   ├── Connection.php      # Client connection wrapper\n│   │   ├── ConnectionPool.php  # Connection management\n│   │   ├── MessageHandler.php  # Event routing\n│   │   ├── WebSocketServer.php # Server implementation\n│   │   └── Events.php          # Event type constants\n│   └── WebPortal/       # Web portal (HTML UI)\n│       ├── WebPortalRouter.php # REST API for portal\n│       └── PageRenderer.php    # Smarty template rendering\n├── Session/            # Playback session management\n├── Media/              # Media library and metadata\n│   ├── Library/        # Library management (LibraryManager, ItemRepository, MediaScanner)\n│   ├── Metadata/      # Metadata fetching (TMDB, TVDB, Fanart, NFO providers)\n│   ├── Transcoding/    # FFmpeg transcoding with EncodingHelper\n│   └── Streaming/      # HLS streaming with adaptive bitrate\n├── Auth/               # Authentication services\n└── Common/             # Shared utilities\n\npublic/\n├── index.php           # Web portal entry point\n├── templates/          # Smarty templates\n└── assets/             # Static assets (css, js)\n```\n\n## Requirements\n\n- **PHP**: 8.3 or higher\n- **MySQL**: 8.0+ or MariaDB 10.6+\n- **Workerman**: 5.0+ (bundled via Composer)\n- **FFmpeg**: For transcoding (optional)\n\n## Features\n\n### Foundation\n- **PSR-11 DI container (PHP-DI 7)**: auto-wired services with provider-based\n  composition; see [phlix-docs / dev / architecture-server](https://detain.github.io/phlix-docs/dev/architecture-server).\n- **PSR-14 event dispatcher (Tukio)**: playback, library-scan, and auth\n  lifecycle events with typed `readonly` DTOs. Plugins subscribe by event\n  class FQCN; see [phlix-docs / dev / event-reference](https://detain.github.io/phlix-docs/dev/event-reference).\n- **Plugin system**: install / enable / disable / uninstall lifecycle,\n  sandboxed per-plugin `vendor/` directories, signature-checked\n  manifests, and PSR-14 event subscription via\n  `Phlix\\Shared\\Plugin\\LifecycleInterface` (the\n  `Phlix\\Plugins\\Contract\\LifecycleInterface` FQCN remains a\n  deprecated bridge through 0.11.x). **Plugin developer guide:**\n  [phlix-docs / plugins / developer-guide](https://detain.github.io/phlix-docs/plugins/developer-guide).\n  Server-internals reference for contributors extending the loader:\n  [phlix-docs / dev / plugin-sdk](https://detain.github.io/phlix-docs/dev/plugin-sdk). Reference\n  plugin: [`detain/phlix-plugin-example`](https://github.com/detain/phlix-plugin-example).\n- **Shared interfaces / DTOs in `detain/phlix-shared`**: framework-neutral\n  Composer package shared with `phlix-hub`. `Phlix\\Shared\\Plugin\\*`,\n  `Phlix\\Shared\\Events\\*`, `Phlix\\Shared\\Auth\\JwtClaims`, and\n  `Phlix\\Shared\\Hub\\*` DTOs live there since `phlix-server` 0.11.0.\n\n### Web Portal\n- **Smarty-based Templates**: Server-side rendered HTML pages using Smarty\n- **REST API Endpoints**: Complete API for library browsing, media info, and user data\n- **JWT Authentication**: Integrated token-based auth with refresh support\n- **Responsive Design**: CSS-first approach with utility classes\n- **JavaScript Client**: ApiClient helper with auth, library, and player helpers\n- **Continue Watching**: Track and display in-progress media\n- **Library Browser**: Browse media by library with item counts\n\n### Authentication \u0026 Security\n- **JWT-based Authentication**: Stateless auth with access tokens (1 hour TTL) and refresh tokens (7 days TTL)\n- **Secure Password Hashing**: Argon2ID for password storage\n- **Multi-Device Sessions**: Track and manage sessions across devices\n- **User Profiles**: Multiple profiles per account with parental controls\n  - Up to 5 profiles per user account\n  - Profile-specific content rating restrictions (G, PG, PG-13, R, NC-17, X, UNRATED)\n  - PIN protection (4 or 6 digits) for profile settings\n  - Genre-based filtering (allowed/blocked genre lists)\n  - Daily watch time limits per profile\n- **Content Rating Filters**: Age-based access restrictions\n- **Audit Logging**: Complete security event logging\n\n### SyncPlay - Group Watching\n- **Synchronized Playback**: Watch content together with friends across devices with sub-second sync accuracy\n- **Host-Controlled Playback**: Only the host can control play/pause/seek; all members receive synchronized commands\n- **NTP-Style Time Sync**: Network time synchronization with latency compensation and drift correction\n- **In-Group Chat**: Real-time messaging with typing indicators and message history\n- **Playback Queue**: Host-managed queue with media info (title, thumbnail)\n- **Host Election**: Automatic host election when current host leaves (oldest member becomes host)\n- **Password Protection**: Optional password protection for private watch parties\n- **Position Tolerance**: Configurable sync tolerance (default 2s) to prevent excessive seeking\n\n### Session Management\n- **Device Sessions**: Track authenticated devices with activity timestamps\n- **Playback Progress**: Resume where you left off across sessions\n- **Continue Watching**: Track items in progress per profile\n- **Watch History**: Complete viewing history per profile with:\n  - Automatic completion detection at 90% progress threshold\n  - Watch time statistics (total, daily, by period)\n  - Resume position tracking for seamless playback continuation\n\n### Live TV \u0026 DVR\n- **Multi-Tuner Support**: DVB-T, DVB-S, DVB-C, and ATSC tuner types\n- **Channel Scanning**: Automatic discovery of broadcast services\n- **Electronic Program Guide**: Full EPG with program info, categories, and search\n- **DVR Scheduling**: Schedule recordings with priority management\n- **Time-Shifting**: Pause and rewind live TV with buffer\n- **Channel Lineups**: Custom channel lineups per user\n- **Favorites**: Personal favorite channels per user\n- **Storage Management**: Recording storage tracking and limits\n\n## Installation\n\n### One-line install (Ubuntu/Debian)\n\nOn a fresh Ubuntu/Debian host, [`scripts/install.sh`](scripts/install.sh) does the whole thing:\nsystem packages (PHP 8.3+, MySQL, ffmpeg), a dedicated `phlix` system user, MySQL database +\nuser, application code, env file at `/etc/phlix/env`, generated `PHLIX_SECRET_KEY`, database\nmigrations, a systemd `phlix-server` service, and an HAProxy reverse proxy with an\nauto-renewing Let's Encrypt certificate.\n\n\u003e The installer also compiles the **Swoole + php-uv** extensions from source (the coroutine\n\u003e runtime Workerman uses), idempotently skipping the build when they already load, and runs a\n\u003e `disable_functions` preflight — see\n\u003e [Swoole \u0026 php-uv on Linux](https://detain.github.io/phlix-docs/install/linux#swoole-php-uv-coroutine-runtime).\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/detain/phlix-server/master/scripts/install.sh | sudo bash\n```\n\nProvision HTTPS in the same run by passing your domain and a Let's Encrypt contact email:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/detain/phlix-server/master/scripts/install.sh \\\n  | sudo bash -s -- --domain phlix.example.com --admin-email you@example.com\n```\n\nThe script prompts for the install path, database user/password, and hostname when run in a\nterminal (with sensible defaults), and runs **fully unattended** when piped or given `-y`. Run\n`sudo bash scripts/install.sh --help` for every flag. Default ports: HTTP on `:8096` behind\nHAProxy on `:80`/`:443`; DLNA discovery on `1900/udp`.\n\n### Install flags\n\n`sudo bash scripts/install.sh --help` lists every option. The most useful:\n\n| Flag | Effect |\n|---|---|\n| `--domain HOST` | Public hostname for the server (enables TLS when paired with `--admin-email`) |\n| `--admin-email EMAIL` | Email registered with Let's Encrypt |\n| `--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. |\n| `--http-port PORT` | HTTP listen port (default `8096`) |\n| `--tmdb-api-key KEY` | TMDB API key for metadata (optional, recorded in `/etc/phlix/env`) |\n| `--hub-url URL` | `PHLIX_HUB_URL` for hub relay (optional) |\n| `--service-user USER` | System user to run as (default `phlix` — dedicated system account, created if missing) |\n| `--branch NAME` | Git branch or tag to install (default `master`) |\n| `--repo URL` | Git repository URL (default `detain/phlix-server`) |\n| `--tls` / `--no-tls` | Force or skip Let's Encrypt + HAProxy TLS |\n| `--no-proxy` | Skip the managed HAProxy entirely (use your own reverse proxy) |\n| `--update` | Pull new code + run migrations on an existing install (preserves env + secrets) |\n| `--uninstall` | Remove the install — interactive prompts before each destructive step |\n| `--purge` | With `--uninstall`, also drop the DB, delete the Let's Encrypt cert, wipe `/var/phlix`, and remove the dedicated system user |\n| `-y`, `--non-interactive` | Never prompt; use defaults/flags |\n| `--interactive` | Force prompts even when piped |\n\n### Updating an existing install\n\nThe same `scripts/install.sh` updates an in-place install **without rotating any secrets**. It\nreads the existing `/etc/phlix/env` (so `DB_PASSWORD` and `PHLIX_SECRET_KEY` are preserved),\npulls the latest code, refreshes Composer dependencies, runs migrations, and restarts the\nservice:\n\n```bash\nsudo bash /var/www/phlix/scripts/install.sh --update -y\n```\n\nPin to a specific tag or branch with `--branch`:\n\n```bash\nsudo bash /var/www/phlix/scripts/install.sh --update --branch v0.2.0 -y\n```\n\n`--update` discovers the install path from the systemd unit's `WorkingDirectory`, fetches code\nas the install dir owner (so it doesn't trip Git's CVE-2022-24765 dubious-ownership check),\nruns `composer install --no-dev --optimize-autoloader`, clears `templates_c/`, runs\n`scripts/run-migrations.php`, restarts `phlix-server`, and curl-checks `/health`. It\ndeliberately leaves the env file, MySQL grants, HAProxy config, and Let's Encrypt cert alone.\n\n### Uninstalling\n\n`scripts/install.sh --uninstall` removes an existing install. It is **interactive by default**\nand prompts separately before each destructive step. The MySQL database, the `/var/phlix` data\ndirectory, and the Let's Encrypt certificate are **kept** unless you opt in:\n\n```bash\nsudo bash /var/www/phlix/scripts/install.sh --uninstall\n```\n\nAdd `--purge` to also drop the database (and user), wipe `/var/phlix` (config, library cache,\nbackups), and delete the Let's Encrypt certificate via `certbot delete`. Combine with `-y` for\na fully unattended teardown:\n\n```bash\nsudo bash /var/www/phlix/scripts/install.sh --uninstall --purge -y\n```\n\nWhat it removes when present:\n\n1. The `phlix-server` systemd unit (`stop`, `disable`, remove file, `daemon-reload`).\n2. HAProxy fragment at `/etc/haproxy/phlix-managed/phlix-server.cfg.fragment`, and\n   `/etc/haproxy/haproxy.cfg` is rebuilt. If phlix-hub is still installed, its frontend +\n   backend stay. If phlix-server was the last Phlix project, the pre-Phlix snapshot at\n   `/etc/haproxy/haproxy.cfg.pre-phlix.bak` is restored (or `haproxy.cfg` is removed and\n   haproxy is stopped + disabled if no snapshot exists).\n3. The combined PEM at `/etc/haproxy/certs/\u003cdomain\u003e.pem`.\n4. `/etc/cron.d/phlix-server-certbot` and the certbot deploy hook.\n5. The Let's Encrypt cert via `certbot delete` — only with `--purge` or interactive confirm.\n6. The MySQL database + user — only with `--purge` or interactive confirm.\n7. The install dir (`/var/www/phlix` by default; system paths refused).\n8. `/var/phlix` (config, library cache, backups) — only with `--purge` or interactive confirm.\n9. `/var/log/phlix` and `/var/run/phlix`.\n10. `/etc/phlix/env` (env file).\n11. The dedicated system user `phlix` via `userdel` — only with `--purge` or interactive\n    confirm. Refuses to touch shared OS accounts (`www-data`, `root`, etc.). Cross-detects\n    phlix-hub's systemd unit and refuses to remove a user that's still being used by it.\n\nSystem packages (`php-*`, `mysql-server`, `ffmpeg`, `haproxy`, `certbot`) and `ufw` rules are\nleft in place — `sudo apt remove …` / `sudo ufw delete …` to remove them.\n\n### Running alongside phlix-hub on the same server\n\nBoth installers can share a single HAProxy instance — they auto-merge into one\n`/etc/haproxy/haproxy.cfg`. Just run both installers normally; the second one detects the\nfirst's fragment and rebuilds a combined config that routes by `Host:` header.\n\n```bash\n# 1. Install phlix-hub first (with TLS).\ncurl -fsSL https://raw.githubusercontent.com/detain/phlix-hub/master/scripts/install.sh \\\n  | sudo bash -s -- --domain hub.example.com --admin-email you@example.com -y\n\n# 2. Install phlix-server, also with TLS, on a different hostname.\ncurl -fsSL https://raw.githubusercontent.com/detain/phlix-server/master/scripts/install.sh \\\n  | sudo bash -s -- --domain phlix.example.com --admin-email you@example.com -y\n```\n\nAfter both finish, `/etc/haproxy/haproxy.cfg` looks like:\n\n```haproxy\n# phlix-managed: rebuilt by phlix install scripts — do not edit\n...\nfrontend fe_https\n    bind :443 ssl crt /etc/haproxy/certs/\n    http-request set-header X-Forwarded-Proto https\n\n    # --- phlix-hub ---\n    acl is_phlix_hub_host hdr(host) -i hub.example.com\n    use_backend be_hub_client_relay if is_phlix_hub_host { path_beg /client/ }\n    use_backend be_hub if is_phlix_hub_host\n\n    # --- phlix-server ---\n    acl is_phlix_server_host hdr(host) -i phlix.example.com\n    use_backend be_phlix_server if is_phlix_server_host\n    ...\n```\n\n**How the merge works.** Each install drops a fragment at\n`/etc/haproxy/phlix-managed/\u003cproject\u003e.cfg.fragment` with `fe_http`, `fe_https`, and `backends`\nsections. A rebuilder function then assembles the final `haproxy.cfg` from every fragment it\nfinds. HAProxy's `crt /etc/haproxy/certs/` directive auto-loads every `.pem` in that directory\nand picks the right one per SNI hostname.\n\nThe first install snapshots any pre-Phlix `haproxy.cfg` to\n`/etc/haproxy/haproxy.cfg.pre-phlix.bak`.\n\n**Uninstall behaviour**: `--uninstall` removes only that project's fragment and rebuilds. If\nother Phlix projects remain, their frontend stays untouched. When the **last** Phlix project\nis uninstalled, the rebuilder restores the pre-Phlix snapshot (or removes `haproxy.cfg`\noutright if there was no pre-Phlix config) and stops/disables `haproxy`.\n\nThe **hub server-tunnel port** (`:8802`) is a separate listener — servers connect to that port\ndirectly. Open it on the firewall but don't put it behind the HAProxy 80/443 frontend.\n\nIf you'd rather use your own reverse proxy (nginx, Caddy, Traefik, etc.) instead of the\nmanaged HAProxy, pass `--no-proxy` to either install script. Each service then listens on its\nown port (8096 for phlix-server, 8800 for phlix-hub) and you point your proxy at those.\n\nEverything else is already namespaced: env files (`/etc/phlix-hub.env` vs `/etc/phlix/env`),\nsystemd units (`phlix-hub.service` vs `phlix-server.service`), install dirs (`/opt/phlix-hub`\nvs `/var/www/phlix`), service users (`www-data` vs `phlix`), MySQL DBs (`phlix_hub` vs\n`phlix`), backend ports (8800/8802/8803 vs 8096), and certbot artefacts.\n\n### Manual install (from source)\n\n```bash\n# Clone the repository\ngit clone https://github.com/detain/phlix-server.git\ncd phlix-server\n\n# Install dependencies\ncomposer install\n\n# Run database migrations (reads config/database.php; password from DB_PASSWORD env var)\nDB_PASSWORD=your_strong_password php bin/phlix migrate    # or: php scripts/run-migrations.php\n\n# Start the server (HTTP + WebSocket on port 8096 from config/server.php)\nphp public/index.php start\n```\n\n## Configuration\n\nConfiguration is managed via PHP files in `config/`:\n\n```php\n// config/server.php\nreturn [\n    'server' =\u003e [\n        'name' =\u003e 'Phlix Media Server',\n        'host' =\u003e '0.0.0.0',\n        'port' =\u003e 8080,\n    ],\n    'websocket' =\u003e [\n        'host' =\u003e '0.0.0.0',\n        'port' =\u003e 8097,\n    ],\n    'database' =\u003e [\n        'host' =\u003e '127.0.0.1',\n        'port' =\u003e 3306,\n        'database' =\u003e 'phlix',\n        'username' =\u003e 'phlix',\n        'password' =\u003e 'secure-password',\n    ],\n    'debug' =\u003e false,\n];\n```\n\n## API Reference\n\n### HTTP Endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| GET | `/health` | Health check |\n| GET | `/system/info` | Server information |\n| POST | `/api/v1/auth/register` | User registration |\n| POST | `/api/v1/auth/login` | User login |\n| POST | `/api/v1/auth/refresh` | Token refresh |\n| GET | `/api/v1/auth/me` | Current user profile |\n| GET | `/api/v1/sessions` | List user sessions |\n| DELETE | `/api/v1/sessions/{id}` | End a session |\n| POST | `/api/v1/sessions/{id}/progress` | Report playback progress |\n| GET | `/api/v1/sessions/{id}/progress` | Get playback state |\n| GET | `/api/v1/admin/settings` | Effective server settings (config default + DB override) — admin-only |\n| PUT | `/api/v1/admin/settings` | Persist server-setting overrides — admin-only |\n| GET | `/api/v1/admin/fs/browse` | List subdirectories under allowed roots (library path picker) — admin-only |\n| GET | `/api/v1/admin/users` | List all users — admin-only |\n| GET | `/api/v1/admin/users/{id}` | Get a specific user — admin-only |\n| POST | `/api/v1/admin/users` | Create a new user — admin-only |\n| PUT | `/api/v1/admin/users/{id}` | Update a user (username, email, password) — admin-only |\n| DELETE | `/api/v1/admin/users/{id}` | Delete a user — admin-only |\n| POST | `/api/v1/admin/users/{id}/set-admin` | Promote or demote a user — admin-only |\n| POST | `/api/v1/admin/users/{id}/reset-password` | Reset a user's password (returns new password) — admin-only |\n\n### WebSocket Events\n\n**Connection Events:**\n- `connected` - Sent on successful connection\n- `client_disconnected` - Broadcast when client disconnects\n\n**Authentication Events:**\n- `auth_request` - Request authentication\n- `auth_success` - Authentication successful\n- `auth_failure` - Authentication failed\n\n**Playback Events:**\n- `playback_start` - Playback started\n- `playback_pause` - Playback paused\n- `playback_stop` - Playback stopped\n- `playback_progress` - Progress update\n- `playback_seek` - Seek performed\n\n**SyncPlay Events:**\n- `syncplay_create_group` - Create watch group\n- `syncplay_join_group` - Join watch group\n- `syncplay_leave_group` - Leave watch group\n- `syncplay_sync_state` - State synchronization\n\n## Development\n\n### Running Tests\n\n```bash\n# Run all tests\n./vendor/bin/phpunit\n\n# Run with coverage\n./vendor/bin/phpunit --coverage-html coverage-report\n\n# Run specific test suite\n./vendor/bin/phpunit --testsuite Unit\n./vendor/bin/phpunit --testsuite Integration\n```\n\n### Code Standards\n\nThis project follows PSR-12 coding standards and uses static analysis tools:\n\n```bash\n# Check code style\n./vendor/bin/phpcs --standard=PSR12 src/\n\n# Run static analysis\n./vendor/bin/phpstan analyze src/ --level=9\n./vendor/bin/psalm\n```\n\n### CLI\n\nAdministrative tasks are exposed through the `bin/phlix` command-line tool (built on `webman/console` / Symfony Console):\n\n```bash\nphp bin/phlix list       # list every available command\nphp bin/phlix migrate    # apply migrations/*.sql against config/database.php\n```\n\n`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.\n\n### Admin SPA (admin-ui)\n\nThe admin console is a **React + TypeScript + Vite** single-page app. Its source lives in\n`admin-ui/`; the production build is emitted into `public/assets/admin/` and **committed to the\nrepo**, so the running Workerman server has **no Node build dependency at runtime** (it just serves\nthe static shell + bundle). `admin-ui/node_modules/` is gitignored.\n\nThe SPA mounts at `/admin` and `/admin/*`, served by `AdminAppController` (returns the built\n`index.html` shell; 503 if the bundle is missing) and gated by the existing `AdminMiddleware` — a\nnon-admin (401/403) is redirected (302) to `/login`. Its typed `ApiClient` reuses the same JWT\nmechanism as `public/assets/js/api-client.js` (`access_token`/`refresh_token` in `localStorage`,\nBearer header, single retry on 401 via `POST /auth/refresh`).\n\n```bash\ncd admin-ui\nnpm install          # one-time / on dependency changes\nnpm run build        # tsc + vite build → ../public/assets/admin/ (commit the result)\nnpm run test         # Vitest unit/component tests\nnpm run dev          # Vite dev server (HMR) for local development\n```\n\nWhen you change anything under `admin-ui/src/`, re-run `npm run build` and commit the refreshed\n`public/assets/admin/` bundle along with your source changes.\n\nCI runs the SPA build + Vitest suite on any change under `admin-ui/` via the **Admin UI** GitHub\nActions workflow (`.github/workflows/admin-ui.yml`): `npm ci → npm run build → npm run test` on\npush/PR to `master`/`main`/`develop` (path-filtered, so PHP-only changes don't trigger it).\n\nThe first feature page on top of the scaffold is the **Libraries** page at `/admin/libraries`\n(step 1.1c): list / add / edit / delete libraries, a `PathPicker` driving the 0.6\n`GET /api/v1/admin/fs/browse` endpoint, per-row Scan / Rescan buttons that hit the **async** 1.1b\nscan API (`POST /api/v1/libraries/{id}/scan|rescan` → 202 `{job_id, status: \"queued\"}`), live\n**coarse** lifecycle status by polling `GET .../scan-status` every 2 s (polling stops on\n`completed` / `failed`), and a per-library scan-history modal. No backend changes — the page\nconsumes only contracts already shipped by 0.6 and 1.1b.\n\nThe **Settings** page at `/admin/settings` (step 1.3) renders all 15 server-setting\nkeys across 8 tabbed groups (Transcoding, Metadata, Markers, Subtitles, Discovery,\nTrickplay, Newsletter, Port Forward). It consumes the 0.5 GET/PUT `/api/v1/admin/settings`\ncontract — **no new endpoints were added**. Bool keys render as toggle switches;\nnumeric keys render as number inputs with `min`/`max` constraints; `tmdb.api_key` renders\nas a password field with Show/Hide toggle. Overridden keys (DB-persisted vs. config-file\ndefault) display a \"custom\" badge. A sticky Save button fires `PUT /api/v1/admin/settings`;\ndirty-state gating keeps the button disabled when no fields have changed.\n\nThe **Webhooks** page at `/admin/webhooks` (step 1.4a) provides full CRUD for webhook\nsubscriptions plus a per-webhook test trigger. It consumes the five endpoint contract in\n`WebhookAdminController` (GET list, POST create, PUT update, DELETE remove, POST test).\nThe `PUT /api/v1/admin/webhooks/{id}` route, plus the backing `WebhookDispatcher::update()`\nand `WebhookAdminController::update()` PHP methods, were added in this step to support\nedit-in-place (the controller already had index/create/delete/test before 1.4a).\nThe event multi-select shows 7 subscribable events grouped into 5 categories\n(Playback, Library, Downloads, Recordings, Alerts); `webhook.test` is internal-only.\nSecret is write-only — GET never returns it; the edit form shows an empty field with\n\"(unchanged)\" placeholder and omits `secret` from the PUT payload when blank, so the\nserver retains the stored value. Coverage: 97.29% on `webhooks.ts`, 89.74% on\n`WebhooksPage.tsx`.\n\nFull operator + contributor docs live in the\n[phlix-docs](https://github.com/detain/phlix-docs) site (`docs/admin/integrations.md`,\n`docs/admin/server-settings.md`, and `docs/dev/admin-spa.md`).\n\n### Git Workflow\n\n1. Create a feature branch: `git checkout -b feature/my-feature`\n2. Make changes and commit: `git commit -am 'Add new feature'`\n3. Push to remote: `git push origin feature/my-feature`\n4. Create Pull Request on GitHub\n5. After review, merge via squash-merge\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch\n3. Ensure all tests pass (`./vendor/bin/phpunit`)\n4. Follow PSR-12 coding standards\n5. Submit a pull request\n\n## License\n\nProprietary - All rights reserved.\n\n## Support\n\nFor issues and feature requests, please use the GitHub issue tracker.\n\n---\n\nFor detailed development documentation, see [DEVELOPER.md](DEVELOPER.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdetain%2Fphlix-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdetain%2Fphlix-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdetain%2Fphlix-server/lists"}