Sign inSign up

ahmadteeb/zauxoflix-backend

By ahmadteeb

โ€ขUpdated 26 days ago

Self-hosted IPTV entitlement, auth & licensing backend for the Zauxoflix app โ€” Next.js + Postgres.

Image
Security
API management
Web servers
0

485

ahmadteeb/zauxoflix-backend repository overview

โ ๐ŸŽฌ Zauxoflix Backend

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.

  • โš™๏ธ Stack: Next.js 16 (App Router) ยท Prisma 7 ยท PostgreSQL 17 ยท JWT auth
  • ๐Ÿ”’ Node-locked license gate (Ed25519) binds a deployment to one host
  • ๐Ÿ“ฑ Companion app: Android ยท Linux ยท Windows
  • ๐Ÿณ Ships build output only โ€” no raw TypeScript source in the image, runs as an unprivileged node user

โ ๐Ÿ†“ Free Trial

Want 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.

โ Trial flow
  1. Email me โ€” I'll reply with your LICENSE_PUBLIC_KEY.
  2. Add it to your .env and start the backend (see Quick start below).
  3. Get your machine ID โ€” open the admin panel, the machine ID is displayed on the license page once the server is running.
  4. Send me the machine ID โ€” I'll issue a node-locked .lic file tied to your host.
  5. Upload the license in the admin panel โ†’ the deployment is fully unlocked.

๐Ÿงท The license is bound to the machine ID of the host. Moving to a different server requires a new license โ€” just email me.


โ ๐Ÿท๏ธ Tags

  • latest โ€” most recent stable build

โ ๐Ÿš€ Quick start (Docker Compose)

The 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).

โ 1. Create docker-compose.yml
services:
  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
โ 2. Create .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"
โ 3. Launch
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.


โ ๐ŸŒ Reverse proxy & SSL

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.com with your actual subdomain throughout.

โ Prerequisites
  • A DNS A record pointing zauxoflix.yourdomain.com โ†’ your server's public IP
  • Ports 80 and 443 open in your firewall / security group
  • The backend running with the loopback port binding shown above (127.0.0.1:3000:3000)
โ 1. Install nginx and certbot
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
โ 2. Create the nginx site config

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;
    }
}
โ 3. Enable the site and test
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
โ 4. Run certbot
sudo certbot --nginx -d zauxoflix.yourdomain.com

Certbot will:

  • Obtain a Let's Encrypt certificate
  • Automatically update the nginx config to add SSL (443) and HTTPโ†’HTTPS redirect
  • Set up a systemd timer for automatic renewal before expiry

Follow the prompts โ€” enter your email for expiry notices and agree to the terms.

โ 5. Verify
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.


โ ๐Ÿ”‘ First steps after boot

  1. Open https://zauxoflix.yourdomain.com/adminโ  (or http://localhost:3000/admin without a proxy) and sign in with ADMIN_EMAIL / ADMIN_PASSWORD.
  2. Activate your deployment with the node-locked .lic file issued for this host's machine-id.
  3. Once activated, manage everything from /admin:
SectionWhat you can do
UsersCreate, disable, and delete user accounts; reset passwords
DevicesView and revoke registered devices per user
Activation codesGenerate and manage codes for granting access
TrialsConfigure trial duration and enable/disable self-signup
SourcesAdd and manage IPTV sources (Xtream / M3U) assigned to users
ReleasesUpload Android, Linux, and Windows app builds served to devices
LicenseView 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.


โ โš™๏ธ Environment variables

VariableRequiredDefaultPurpose
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_SECONDS900Access-token lifetime
REFRESH_TOKEN_TTL_DAYS90Refresh-token lifetime
MACHINE_ID_PATH/etc/host-machine-idLicense fingerprint source
RELEASES_DIR/data/releasesUploaded app builds
SMTP_*(unset)Optional email (reset codes, expiry warnings)
LICENSE_EXPIRY_WARN_DAYS14Days before expiry to email admins

โ ๐Ÿ’พ Data & backups

VolumeHoldsBack up with
zauxoflix-db-dataAccounts, entitlements, watch progress, licensedocker compose exec db pg_dump -U zauxoflix zauxoflix > backup.sql
zauxoflix-releases-dataUploaded app binariescopy the volume out

โ ๐Ÿ›ก๏ธ Security notes

  • Postgres is not published to the host โ€” only the backend reaches it over the compose network.
  • Secrets have no defaults: the app refuses to boot with an empty/known ADMIN_PASSWORD, JWT_SECRET, or LICENSE_PUBLIC_KEY.
  • The image contains no raw source and runs as an unprivileged user.
  • Put the backend behind a TLS-terminating reverse proxy (Caddy, nginx, Traefik) in production.

Proprietary software. ยฉ Zauxoflix. All rights reserved.

Tag summary

Content type

Image

Digest

sha256:0ab28a55aโ€ฆ

Size

274.4 MB

Last updated

26 days ago

docker pull ahmadteeb/zauxoflix-backend