Skip to content

Architecture

A one-page tour of how tunlx is built. Useful if you're debugging a weird flow, deciding how to slot tunlx into your stack, or sizing a deployment.

System view

flowchart TB
    classDef edge   fill:#0ea5e9,stroke:#0369a1,color:#fff
    classDef admin  fill:#22d3ee,stroke:#0e7490,color:#0f172a
    classDef core   fill:#6366f1,stroke:#4338ca,color:#fff
    classDef mod    fill:#1e293b,stroke:#334155,color:#e2e8f0
    classDef egress fill:#0f766e,stroke:#0d9488,color:#fff
    classDef src    fill:#a855f7,stroke:#7e22ce,color:#fff
    classDef store  fill:#f59e0b,stroke:#b45309,color:#0f172a

    Browser["Operator browser"]:::edge
    Client["IPTV / Xtream client"]:::edge

    subgraph TUNLX["tunlx process"]
      direction TB
      Mux["net/http mux<br/>(auth · CSP · rate limit · CORS)"]:::core

      Dashboard["Dashboard<br/>+ Web Player"]:::admin
      WizardAPI["Setup + Xtream wizard APIs"]:::admin
      MetricsAPI["/metrics · /tailnet · /networking"]:::admin

      subgraph PROXIES["Proxy layer"]
        Standard["Standard proxy<br/>(CustomReverseProxy)"]:::core
        Composite["Composite proxy"]:::core
        Xtreme["Xtream service"]:::core
      end

      subgraph SERVICES["Background services"]
        EPG["EPG scheduler<br/>(epgAutoRefresh)"]:::mod
        VPNSched["VPN scheduler<br/>(Mullvad rotation)"]:::mod
        Health["Stream tester"]:::mod
        Activity["Activity log<br/>(last 200 events)"]:::mod
        MFBridge["MediaFlow bridge"]:::mod
      end

      Config["config.json<br/>(load · validate · save)"]:::store
    end

    subgraph EGRESS["Egress modes"]
      direction TB
      System["system route"]:::egress
      WG["WireGuard (wg0 + SO_BINDTODEVICE)"]:::egress
      TS["Tailscale exit node"]:::egress
      Direct["direct (sourceIP)"]:::egress
    end

    Upstreams["Upstream IPTV providers"]:::src
    MediaFlow["MediaFlow Proxy"]:::src
    IPTVOrg["iptv-org catalog"]:::src

    Browser --> Mux
    Client  --> Mux

    Mux --> Dashboard
    Mux --> WizardAPI
    Mux --> MetricsAPI
    Mux --> PROXIES

    Dashboard -. controls .-> PROXIES
    Dashboard -. controls .-> SERVICES
    Dashboard -. reads .-> Config
    Dashboard -. writes .-> Config
    WizardAPI -. writes .-> Config
    PROXIES   -. reads .-> Config

    Composite --> Standard
    Standard  --> MFBridge
    Xtreme    --> MFBridge
    MFBridge  --> MediaFlow

    Standard  --> EGRESS
    Xtreme    --> EGRESS
    MediaFlow --> EGRESS

    EGRESS  --> Upstreams
    Xtreme  --> IPTVOrg

    VPNSched -. rotates .-> WG
    EPG      -. fetches .-> Upstreams
    Health   -. probes  .-> Standard
    Health   -. probes  .-> Xtreme

    Standard --> Activity
    Composite --> Activity
    Xtreme   --> Activity

Process model

tunlx is a single Go binary, listening on:

  • The dashboard port (default 6060) — admin UI, web player, all admin APIs, plus /metrics.
  • One listener per proxy / composite / Xtream service — each on its own host/port from config.json.

There's no separate worker pool. Background jobs (EPG, VPN scheduler, stream tester) are goroutines inside the main process. This is deliberate — one binary, one config file, one log stream.

HTTP plumbing

  • Router: stdlib net/http mux only. No gorilla/mux, gin, chi.
  • Auth: dashboard-level. webCredentials → bcrypt verify → signed session cookie via gorilla/securecookie + gorilla/sessions.
  • Middleware chain:
  • AuthMiddleware on admin routes
  • securityHeadersMiddleware (CSP, HSTS, X-Frame-Options)
  • loggingMiddleware
  • applyCORSHeaders
  • Rate limiting: tollbooth with three buckets: general (60 r/s burst 180), EPG (180 r/s burst 360), login (0.5 r/s burst 2 — deters brute force).

Proxy flavors at a glance

Flavor Code path Best for
Standard CustomReverseProxy in tunlx.go One upstream, optional MediaFlow + EPG
Composite xtreme_group_streams.go + composite mux Curated multi-provider catalog
Xtream service xtreme_*.go Your own panel, with users and groups

Config lifecycle

flowchart LR
    classDef io   fill:#0ea5e9,stroke:#0369a1,color:#fff
    classDef proc fill:#6366f1,stroke:#4338ca,color:#fff
    classDef data fill:#f59e0b,stroke:#b45309,color:#0f172a

    Start["./tunlx"]:::io
    Load["config.go: load + validate"]:::proc
    Default["Generate defaults if missing"]:::proc
    Boot["startProxiesFromConfig()"]:::proc
    JSON["config.json"]:::data
    DashWrite["Dashboard / API writes"]:::proc
    Save["Atomic write"]:::proc

    Start --> Load --> JSON
    Load -. missing .-> Default --> JSON
    Load --> Boot
    DashWrite --> Save --> JSON
    JSON -. read .-> Boot
  • All persisted state lives in config.json. No database.
  • Writes from the dashboard use atomic replace so a crashed write can't corrupt the file.
  • A few "live" overrides (verbose flags) take effect immediately without a restart; inbound-mode changes trigger a clean in-place re-exec.

Where each subsystem lives

Concern File(s)
Main, mux, dashboard tunlx.go
Config schema + load config.go
Standard proxy logic tunlx.go (CustomReverseProxy)
Composite resolution xtreme_group_streams.go, xtream_augment.go
Xtream service xtreme_*.go
Stream protocol detect stream_protocol.go
Stream health tests xtreme_stream_tester.go
iptv-org catalog iptv_org.go
WireGuard / VPN vpn/, vpn_scheduler.go
Tailscale (tsnet) tailnet/, tailnet_config.go, tailnet_handlers.go
Networking modes networking_handlers.go, inbound_endpoints.go
Templates templates/dashboard.html, templates/player.html
Static assets static/

Request flow: live stream

sequenceDiagram
    participant C as Xtream client
    participant P as Standard proxy
    participant M as MediaFlow (optional)
    participant U as Upstream

    C->>P: GET /live/user/pass/123.ts
    P->>P: Auth + rate limit + activity policy match
    alt mediaFlowEnabled
        P->>M: GET /proxy/stream?url=upstream
        M->>U: Range-aware GET upstream
        U-->>M: Stream
        M-->>P: Stream
    else
        P->>U: GET upstream (route: system / vpn / tsexit / direct)
        U-->>P: Stream
    end
    P-->>C: Stream + bandwidth counted

Build & deploy targets

Target Detail
GOARCH amd64 (CI build). int is 64-bit, which matters for Xtream stream IDs.
Container image ghcr.io/eyupio/tunlx:latest — Ubuntu 22.04 base, ffmpeg preinstalled.
Deploy units Root docker-compose.yaml (host networking) plus a gluetun-sidecar variant under deployments/docker/.
Installer install.sh — interactive, silent, and dry-run modes.

Performance notes

  • Allocation-light hot paths. Stream protocol detection uses strings.EqualFold instead of ToLower to avoid per-segment allocs.
  • Bandwidth counters flush at 64KB so low-bitrate streams still appear in the live UI.
  • int64 stream IDs. Xtream stream_id / series_id exceed int32 in the wild. Clamp logic deliberately rejects only NaN / ±Inf / true int64 overflow.