Skip to content

Deployment Guide

Prerequisites

  • Ubuntu 20.04+ or Debian 11+
  • PHP 8.1+ with extensions: curl, mysqli, json, mbstring
  • MariaDB 10.3+ or MySQL 5.7+
  • nginx with PHP-FPM
  • Python 3.8+ with pywidevine dependencies
  • xaccel-codec server (separate service)
  • SSL certificate for the panel domain

Installation

1. Clone the Repository

git clone git@git.retrorewind.ca:iptv/xctv-dazn-drm-panel.git /opt/xctv-dazn-drm-panel

The web root is the dazn.xctv.stream/ subdirectory inside the repo. Create a symlink so nginx can serve it:

sudo ln -s /opt/xctv-dazn-drm-panel/dazn.xctv.stream /var/www/dazn.xctv.stream

2. Database Setup

Create the database and import the schema:

mysql -u root -p < dazn-xctv.sql

Or create manually:

CREATE DATABASE IF NOT EXISTS dazn CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

Then import dazn-xctv.sql which creates all tables (settings, sports, competition, competition_groups, epg_templates, schedule, log, proxy, users, user_2fa_remember, user_api_keys).

3. Database Configuration

Edit dazn.xctv.stream/db.inc.php with your database credentials. The file uses environment variables with fallback defaults:

$host     = getenv('XCtv_dbHost') ?: '127.0.0.1';
$user     = getenv('XCtv_dbUser') ?: 'dazn';
$password = getenv('XCtv_dbPass') ?: '';
$database = getenv('XCtv_dbName') ?: 'dazn';

Set environment variables in your PHP-FPM pool or .env file:

export XCtv_dbHost=192.168.1.85
export XCtv_dbUser=dazn
export XCtv_dbPass=YOUR_PASSWORD_HERE
export XCtv_dbName=dazn

4. nginx Configuration

Copy the provided nginx config:

sudo cp /opt/xctv-dazn-drm-panel/etc/dazn.xctv.stream.conf /etc/nginx/conf.d/dazn.xctv.stream.conf
sudo nginx -t && sudo systemctl reload nginx

Before reloading, edit the config to match your environment:

  • SSL certificate paths — update ssl_certificate and ssl_certificate_key
  • PHP-FPM socket — the config ships with php8.1-fpm.sock; adjust to match your installed version (e.g. php8.3-fpm.sock)
  • Server name — change dazn.xctv.stream to your domain

The config includes:

  • Sensitive-path blocking.git and pywidevine/ requests return 404.
  • PHP-source protectiondb.inc.php, function.php, and auth.php are routed through PHP-FPM via a priority location block so their source code is never served raw if the engine fails.
  • Clean API URL/api rewrites to /api.php; HTTP_AUTHORIZATION is forwarded to PHP-FPM for Bearer token auth.
  • Extensionless PHP/foo transparently rewrites to /foo.php if the file exists, enabling clean URLs throughout the panel.
  • XML downloadlocation ~* \.xml$ forces EPG files to download as attachments rather than rendering in the browser.

5. File Permissions

Use the included hardening script:

sudo bash /opt/xctv-dazn-drm-panel/bin/set-permissions.sh

Or apply manually:

sudo chown -R www-data:www-data /var/www/dazn.xctv.stream
sudo find /var/www/dazn.xctv.stream -type d -exec chmod 750 {} \;
sudo find /var/www/dazn.xctv.stream -type f -exec chmod 640 {} \;

The panel needs write access to:

  • api_endpoints.json (endpoint cache)
  • token.txt / refresh_token.txt (auth tokens)
  • cron_dazn (generated cron entries)
  • xctv-dazn-epg.xml (generated EPG, filename configurable via epg_filename setting)

The sidebar displays the current git revision. This reads .git/HEAD directly (no shell_exec needed), so www-data must have read access to the .git/ directory. The chown -R above handles this.

6. Python Dependencies

cd /var/www/dazn.xctv.stream
pip3 install pywidevine protobuf requests curl_cffi

curl_cffi provides Chrome TLS fingerprint impersonation required for DASZ Playback and license API calls (CloudFront WAF blocks standard Python/cURL TLS fingerprints).

7. Cron Jobs

The panel auto-generates per-event cron entries in /etc/cron.d/cron_dazn. You need two system-level cron jobs:

# Build tomorrow's schedule at 23:58
58 23 * * * root /usr/bin/php /var/www/dazn.xctv.stream/cron_schedule.php

# Check and stop finished streams every 5 minutes
*/5 * * * * root /usr/bin/php /var/www/dazn.xctv.stream/reset_status.php

Initial Configuration

  1. Navigate to https://dazn.xctv.stream/config_dazn
  2. Enter your DAZN credentials (email/password)
  3. The device ID is auto-generated on first use (valid UUID)
  4. Set the xaccel-codec URL and token
  5. Configure CDN preferences in the cdn_takes field (JSON array)
  6. Click "Refresh API Endpoints from DAZN" to populate endpoint cache
  7. Add sports and competitions via the sidebar menu, or use the Discover IDs tool to scan them from DAZN's EPG

Troubleshooting

Symptom Likely Cause Fix
Token request error Credentials wrong/expired Update in Global Config
Empty schedule after cron Endpoints stale or API changed Click "Refresh API Endpoints"
NO CDN FOUND EXIT in log cdn_takes doesn't match CDNs Check log for CDN names, update config
Stream starts, no video DRM keys failed Check request_keys.py connectivity
Playback 403 CloudFront TLS fingerprint blocked Ensure curl_cffi is installed: pip3 install curl_cffi
xaccel-codec "No decryption key" Keys not applied Check xaccel-codec panel version supports dynamic-url API
PHP deprecation warnings PHP 8.2+ ${var} syntax Fixed in v1.2.0

Documentation Site

Project docs are served as a self-hosted MkDocs Material site at https://docs.xctv.stream/dazn/. The CI pipeline builds and deploys docs on every push to the default branch.

Setup

sudo mkdir -p /var/www/docs.xctv.stream
sudo cp etc/docs.xctv.stream.conf /etc/nginx/sites-available/
sudo ln -s /etc/nginx/sites-available/docs.xctv.stream.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

The nginx config (etc/docs.xctv.stream.conf) serves each project's docs as a subfolder (e.g. /dazn/, /prime/).

CI/CD Pipeline

All pipeline jobs are pinned to runners tagged drm via the default.tags directive. The pipeline has three jobs:

Job Stage Trigger Description
build_docs build Push to default branch Builds MkDocs site to public/
deploy_docs deploy After build_docs Rsyncs public/ to docs server
deploy_panel deploy Push to default branch (when dazn.xctv.stream/ changes) Rsyncs panel files to web root

The pipeline requires these GitLab CI/CD variables (docs and panel deploy to different servers, so each has its own set):

Variable Used by Description
DOCS_SSH_KEY deploy_docs Ed25519 private key for the docs server
DOCS_SSH_KNOWN_HOSTS deploy_docs SSH known_hosts entry for the docs server
DOCS_DEPLOY_USER deploy_docs SSH username on the docs server
DOCS_DEPLOY_HOST deploy_docs Hostname of the docs server
PANEL_SSH_KEY deploy_panel Ed25519 private key for the panel server
PANEL_SSH_KNOWN_HOSTS deploy_panel SSH known_hosts entry for the panel server
PANEL_DEPLOY_USER deploy_panel SSH username on the panel server
PANEL_DEPLOY_HOST deploy_panel Hostname of the panel server

The deploy_panel job excludes sensitive and non-web files from the rsync (.git*, pywidevine/, db.inc.php, *.py).