---
url: https://live777.pages.dev/guide/liveman.md
---
# Liveman (Live777 Manager)

Live777 Cluster manager.

If I have so many servers (live777 core cluster), I need this manage them all. Liveman also provides centralized recording index management and playback proxy services for the entire cluster.

## Database Configuration

Liveman stores the recording index (mapping stream + date → mpd\_path) in a database. Migrations run automatically at startup.

* Default driver: SQLite (embedded)
* Supported drivers: SQLite, PostgreSQL (via SeaORM `DATABASE_URL`)

```toml
[database]
# Default database URL (SQLite)
# Environment variable: DATABASE_URL
# Default value if not set: sqlite://./liveman.db
url = "sqlite://./liveman.db?mode=rwc"

# Maximum number of database connections
# Default: 10
max_connections = 10

# Connection timeout in seconds
# Default: 30
connect_timeout = 30
```

Example for PostgreSQL

```toml
[database]
url = "postgresql://user:password@localhost:5432/live777"
max_connections = 10
connect_timeout = 30
```

## Recording Index and Storage

The recording system stores the index (date-based manifest location) in the database while keeping the actual media files in the configured storage backend (filesystem or S3).

Liveman also exposes a storage API used by Liveion's async upload queue (S3 only):

* `POST /api/storage/presign` with `{ "method": "PUT", "path": "object", "ttl_seconds": 300 }` — generates a presigned URL; requires S3
* `GET /api/storage/ping` — checks storage availability

### Recording Index Schema

Table: `recordings` (auto-created by migrations)

* **id**: UUID, primary key
* **stream**: String, stream identifier
* **year**: Integer
* **month**: Integer
* **day**: Integer
* **mpd\_path**: String, path to the manifest within storage
* **created\_at**: Timestamp with time zone
* **updated\_at**: Timestamp with time zone

Unique index: `(stream, year, month, day)`

## Authentication

### No Authentication {#noauth}

References: [live777#Authentication/No Authentication](/guide/live777#noauth)

### Bearer token {#token}

References: [live777#Authentication/Bearer token](/guide/live777#token)

### JWT(JSON Web Token) {#JWT}

References: [live777#Authentication/JWT(JSON Web Token)](/guide/live777#JWT)

### Username/password

Login to dashboard, manager cluster and stream using username/password

```toml
[auth]
Admin Dashboard Accounts

[[auth.accounts]]
username = "live777"
password = "live777"

[[auth.accounts]]
username = "live777-2"
password = "live777-2"
```

## Extra `IceServers` {#extra-ice}

By default neither liveman nor live777 advertises any ICE server: liveman
adds none and only passes through the upstream liveion `Link` headers, and
liveion itself defaults to an empty `[[ice_servers]]` list. An unconfigured
deployment therefore stays fully on the LAN (host candidates only). Configure
this section only for internet-facing deployments.

This merge all `iceServers` in `WHIP`/`WHEP`

::: danger NOTE:
**This only work `WHIP`/`WHEP` Client**

The `WHIP`/`WHEP` Server don't support trickle ICE. If Server use a lot ice is to slow
:::

If don't need upstream `ice_servers`, You can use `override_upstream_ice_servers = true` for drop

```toml
[extra_ice]
# When WHIP/WHEP use liveman proxy
# liveman override upstream liveion http header link ice_servers
override_upstream_ice_servers = false
```

### Static {#static}

Look like `live777 config [ice_servers]`

```toml
[[extra_ice.ice_servers]]
urls = [
    "stun:stun.22333.fun",
    "stun:cn.22333.fun",
    "stun:stun.l.google.com:19302",
]

[[extra_ice.ice_servers]]
urls = [ "turn:turn.22333.fun", "turn:cn.22333.fun" ]
username = "live777"
credential = "live777"
```

### Coturn {#coturn}

In production environment, We for each session use different turn username / password

::: danger NOTE:
Coturn `--use-auth-secret` and `--lt-cred-mech` is conflict
:::

```toml
[extra_ice.coturn]
# Coturn must use: --use-auth-secret
# The secret is: --static-auth-secret=live777
secret = "live777"
urls = [
    "stun:coturn.22333.fun:3478",
    "turn:coturn.22333.fun:3478?transport=udp",
    "turn:coturn.22333.fun:3478?transport=tcp",
]
ttl = 3600
```

### Cloudflare {#cloudflare}

Use Cloudflare turn servers

```toml
[extra_ice.cloudflare]
# https://developers.cloudflare.com/realtime/turn/generate-credentials/
key_id = ""
api_token = ""
ttl = 3600
```

## Node Status Updates {#status}

Liveman can receive Liveion stream status via push instead of relying solely on polling.

### SSE (for static/manual nodes)

For every node configured under `[[nodes]]`, Liveman automatically opens an SSE connection to the Liveion `/api/sse/streams` endpoint and updates its local storage when stream state changes.

### net4mqtt xdata (for net4mqtt-discovered nodes)

When `net4mqtt` is enabled, each Liveion node pushes a stream snapshot through `xdata` with key `streams` whenever the stream state changes, and on the 2-second media-stats sampling tick when the reported counters change (payloads are deduplicated, so an idle node stays silent). Liveman decodes it and updates the corresponding node entry.

When Liveman merges the same stream across multiple nodes, the merged `stats` field is marked with `statsScope: "clusterNodeWork"`. It is a sum of node-level work, not cluster edge traffic: cascade hops are counted once on each relay node.

## Node Capacity Policy {#node-capacity}

Liveman uses a per-node subscriber capacity for WHEP routing and cascade target selection. This is a liveman-side scheduling policy only; liveion does not enforce it.

Static nodes take an optional `sub_max` (absent = unlimited):

```toml
[[nodes]]
alias = "edge-0"
token = "live777"
url = "http://127.0.0.1:7777"
# Per-stream subscriber capacity for WHEP routing and cascade target
# selection (absent = unlimited)
sub_max = 1
```

Nodes without an explicit `[[nodes]]` entry — primarily dynamically discovered (net4mqtt) nodes — take their policy from the first matching `[[node_rules]]` glob (no match = unlimited). An explicit `[[nodes]] sub_max` always wins over the rules:

```toml
[[node_rules]]
pattern = "edge-*"
sub_max = 1

# Catch-all: unknown nodes may publish (act as sources) but never serve a
# viewer directly, so cascades never land on an unrecognized node
[[node_rules]]
pattern = "*"
sub_max = 0
```

## Cluster {#cluster}

Cluster mode must liveman. We can use [`net4mqtt`](/guide/net4mqtt) extra network

![liveman-cluster](/liveman-cluster.excalidraw.svg)

## Verge {#verge}

We support cloud and verge mix cluster

![live777-verge](/live777-verge.excalidraw.svg)
