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
pywidevinedependencies - xaccel-codec server (separate service)
- SSL certificate for the panel domain
Installation¶
1. Clone the Repository¶
The web root is the dazn.xctv.stream/ subdirectory inside the repo.
Create a symlink so nginx can serve it:
2. Database Setup¶
Create the database and import the schema:
Or create manually:
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_certificateandssl_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.streamto your domain
The config includes:
- Sensitive-path blocking —
.gitandpywidevine/requests return 404. - PHP-source protection —
db.inc.php,function.php, andauth.phpare routed through PHP-FPM via a priority location block so their source code is never served raw if the engine fails. - Clean API URL —
/apirewrites to/api.php;HTTP_AUTHORIZATIONis forwarded to PHP-FPM for Bearer token auth. - Extensionless PHP —
/footransparently rewrites to/foo.phpif the file exists, enabling clean URLs throughout the panel. - XML download —
location ~* \.xml$forces EPG files to download as attachments rather than rendering in the browser.
5. File Permissions¶
Use the included hardening script:
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 viaepg_filenamesetting)
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¶
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¶
- Navigate to
https://dazn.xctv.stream/config_dazn - Enter your DAZN credentials (email/password)
- The device ID is auto-generated on first use (valid UUID)
- Set the xaccel-codec URL and token
- Configure CDN preferences in the
cdn_takesfield (JSON array) - Click "Refresh API Endpoints from DAZN" to populate endpoint cache
- 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).