Self-hosted IPTV entitlement, auth & licensing backend for the Zauxoflix app โ Next.js + Postgres.
485
Self-hosted backend for Zauxoflix, a white-label, single-tenant IPTV app. It handles user accounts, device entitlement, trials & activation codes, cross-device watch sync, TV pairing, app-release hosting, and node-locked licensing โ while video always streams directly from the user's own IPTV provider to their device. The backend never proxies or stores stream content.
node userWant to try Zauxoflix on your server? Contact me at [email protected]โ and I'll set you up with a trial license.
I'll also build the companion apps (Android, Linux, Windows) configured for your server URL.
LICENSE_PUBLIC_KEY..env and start the backend (see Quick start below)..lic file tied to your host.๐งท The license is bound to the machine ID of the host. Moving to a different server requires a new license โ just email me.
latest โ most recent stable buildThe backend needs a PostgreSQL database. The compose file below runs both and wires them together on a private network (Postgres is not exposed to the host).
docker-compose.ymlservices:
db:
container_name: postgres
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: zauxoflix
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-zauxoflix}"
POSTGRES_DB: zauxoflix
# Deliberately NOT published to the host: the database holds IPTV
# credentials and password hashes, and only the backend needs to reach it
# over the compose network. Use `docker compose exec db psql` for admin.
volumes:
- db_data:/var/lib/postgresql/data
networks:
net:
ipv4_address: 172.30.0.3
healthcheck:
test: ["CMD-SHELL", "pg_isready -U zauxoflix -d zauxoflix"]
interval: 5s
timeout: 5s
retries: 10
backend:
image: ahmadteeb/zauxoflix-backend:latest
container_name: zauxoflix
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
# Inside the compose network the database host is `db`, not localhost.
DATABASE_URL: "postgresql://zauxoflix:${POSTGRES_PASSWORD:-zauxoflix}@db:5432/zauxoflix?schema=public"
ADMIN_EMAIL: "${ADMIN_EMAIL:[email protected]}"
# No default: the seed refuses to create an admin with a known password.
ADMIN_PASSWORD: "${ADMIN_PASSWORD:?set ADMIN_PASSWORD in the environment}"
JWT_SECRET: "${JWT_SECRET:?set JWT_SECRET in the environment}"
ACCESS_TOKEN_TTL_SECONDS: "${ACCESS_TOKEN_TTL_SECONDS:-900}"
REFRESH_TOKEN_TTL_DAYS: "${REFRESH_TOKEN_TTL_DAYS:-90}"
LICENSE_PUBLIC_KEY: "${LICENSE_PUBLIC_KEY:?set LICENSE_PUBLIC_KEY in the environment}"
# Fingerprint the host, not the container, so the license binds to the box.
MACHINE_ID_PATH: "/etc/host-machine-id"
# Uploaded app binaries served by the public landing page live on a volume.
RELEASES_DIR: "/data/releases"
NODE_ENV: production
volumes:
- ${MACHINE_ID_PATH}:/etc/host-machine-id:ro
- releases_data:/data/releases
networks:
net:
ipv4_address: 172.30.0.2
volumes:
# Back up with: docker compose exec db pg_dump -U zauxoflix zauxoflix > backup.sql
db_data:
name: zauxoflix-db-data
# Uploaded app builds. Survives container rebuilds; back up by copying it out.
releases_data:
name: zauxoflix-releases-data
networks:
net:
name: zauxoflix-network
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/29
gateway: 172.30.0.1
.env next to it# Copy to .env for local/host development.
# Docker Compose overrides DATABASE_URL to use host `db` (see docker-compose.yml).
# Under compose the database is NOT published to the host; POSTGRES_PASSWORD is
# required and must be strong: `openssl rand -base64 24`.
POSTGRES_PASSWORD="change-me"
DATABASE_URL="postgresql://zauxoflix:${POSTGRES_PASSWORD}@localhost:5432/zauxoflix?schema=public"
# Seed admin (created by `npm run db:seed`). ADMIN_PASSWORD has no default โ
# the seed refuses to run without one. Pick something strong.
ADMIN_EMAIL="[email protected]"
ADMIN_PASSWORD=""
# Auth. Generate a strong secret for production: `openssl rand -base64 48`.
JWT_SECRET="change-me"
# Encrypts stored IPTV source passwords. Independent of JWT_SECRET so token
# signing can be rotated safely. Generate: openssl rand -base64 32
# When unset, the key is derived from JWT_SECRET (legacy behaviour) and
# rotating JWT_SECRET makes every stored IPTV password undecryptable.
SOURCE_ENCRYPTION_KEY=""
ACCESS_TOKEN_TTL_SECONDS="900"
REFRESH_TOKEN_TTL_DAYS="90"
# License gate (node-locked activation).
# Public key generated by vendor (base64 SPKI DER).
LICENSE_PUBLIC_KEY=""
# Path to the host machine-id used as the fingerprint source.
MACHINE_ID_PATH="/etc/machine-id"
# SMTP (optional). When SMTP_HOST is unset, self-service "forgot password"
# email is disabled and users must get reset codes from the admin. It also
# gates the license-expiry notifications below.
SMTP_HOST=""
SMTP_PORT="587"
# "true" for implicit TLS (port 465 implies it automatically).
SMTP_SECURE="false"
SMTP_USER=""
SMTP_PASS=""
SMTP_FROM="Zauxoflix <[email protected]>"
# Days before expiry to start emailing admins a renewal warning (requires SMTP).
LICENSE_EXPIRY_WARN_DAYS="14"
docker compose up -d
On first boot the container applies database migrations, seeds the admin account (idempotent), then serves on http://localhost:3000โ . Health check: GET /api/healthz.
Running Zauxoflix on a real domain with HTTPS requires a TLS-terminating reverse proxy in front of the container. The instructions below use nginx + Let's Encrypt (certbot) on a Linux host. Certbot handles certificate issuance, nginx SSL configuration, and auto-renewal automatically.
Replace
zauxoflix.yourdomain.comwith your actual subdomain throughout.
zauxoflix.yourdomain.com โ your server's public IP127.0.0.1:3000:3000)sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
Create /etc/nginx/sites-available/zauxoflix:
server {
listen 80;
listen [::]:80;
server_name zauxoflix.yourdomain.com;
# Must match MAX_RELEASE_BYTES in the backend (2 GiB) for app-binary uploads.
client_max_body_size 2g;
location / {
proxy_pass http://172.30.0.2:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
# X-Real-IP is assigned (not appended) so clients cannot spoof it.
# The backend rate-limiter keys on this header.
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Required for Server-Sent Events (live sync / watch progress).
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_request_buffering off;
proxy_read_timeout 1d;
proxy_send_timeout 1d;
chunked_transfer_encoding on;
add_header X-Accel-Buffering no;
}
}
sudo ln -s /etc/nginx/sites-available/zauxoflix /etc/nginx/sites-enabled/zauxoflix
sudo nginx -t # must print "syntax is ok"
sudo systemctl reload nginx
sudo certbot --nginx -d zauxoflix.yourdomain.com
Certbot will:
Follow the prompts โ enter your email for expiry notices and agree to the terms.
curl -sI https://zauxoflix.yourdomain.com/api/healthz
# Expected: HTTP/2 200
The admin panel is now reachable at https://zauxoflix.yourdomain.com/adminโ . Use this URL when configuring the companion apps.
http://localhost:3000/admin without a proxy) and sign in with ADMIN_EMAIL / ADMIN_PASSWORD..lic file issued for this host's machine-id./admin:| Section | What you can do |
|---|---|
| Users | Create, disable, and delete user accounts; reset passwords |
| Devices | View and revoke registered devices per user |
| Activation codes | Generate and manage codes for granting access |
| Trials | Configure trial duration and enable/disable self-signup |
| Sources | Add and manage IPTV sources (Xtream / M3U) assigned to users |
| Releases | Upload Android, Linux, and Windows app builds served to devices |
| License | View license status, expiry, and upload a new .lic file |
๐งท The license is bound to
/etc/machine-id. Keep the read-only mount in place โ moving to a new host requires a new license.
| Variable | Required | Default | Purpose |
|---|---|---|---|
DATABASE_URL | โ | โ | Postgres connection string |
ADMIN_EMAIL | โ | [email protected] | Seed admin login |
ADMIN_PASSWORD | โ | (none) | Seed admin password โ no default |
JWT_SECRET | โ | (none) | Signs access/refresh tokens |
LICENSE_PUBLIC_KEY | โ | (none) | Verifies the deployment license |
ACCESS_TOKEN_TTL_SECONDS | 900 | Access-token lifetime | |
REFRESH_TOKEN_TTL_DAYS | 90 | Refresh-token lifetime | |
MACHINE_ID_PATH | /etc/host-machine-id | License fingerprint source | |
RELEASES_DIR | /data/releases | Uploaded app builds | |
SMTP_* | (unset) | Optional email (reset codes, expiry warnings) | |
LICENSE_EXPIRY_WARN_DAYS | 14 | Days before expiry to email admins |
| Volume | Holds | Back up with |
|---|---|---|
zauxoflix-db-data | Accounts, entitlements, watch progress, license | docker compose exec db pg_dump -U zauxoflix zauxoflix > backup.sql |
zauxoflix-releases-data | Uploaded app binaries | copy the volume out |
ADMIN_PASSWORD, JWT_SECRET, or LICENSE_PUBLIC_KEY.Proprietary software. ยฉ Zauxoflix. All rights reserved.
Content type
Image
Digest
sha256:0ab28a55aโฆ
Size
274.4 MB
Last updated
26 days ago
docker pull ahmadteeb/zauxoflix-backend