Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ The Server's MediaMTX instance maintains a continuous background loop (`/offline

To prevent rapid stream flapping during minor cellular jitters, the Client employs a **State Latch** on the MLVPN tunnel utilizing a **Network Hysteresis** logic. The Client continuously attempts to ping the Server tunnel (`10.1.10.1`). Instead of a binary success/fail check, the system performs a retry loop (up to 3 consecutive ping attempts with a small delay). It will only drop the bonded UDP route and fallback to a standard unbonded route if the tunnel explicitly stays down and all 3 ping checks fail.

Furthermore, to ensure application stability before initializing the FFmpeg encoder pipeline, the core implements **Strict URL Validation** on both `RTSP` and `SRT` streaming endpoints using Go's native `net/url` parser. The ingest will gracefully reject malformed URLs prior to execution.
Furthermore, to ensure application stability before initializing the FFmpeg encoder pipeline, the core implements **Strict URL Validation** on streaming endpoints such as `SRT` using Go's native `net/url` parser. The ingest will gracefully reject malformed URLs prior to execution.

## Security & Execution Flow

Expand Down
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,28 @@ Note: Adjust the port (`8080` by default) to match your `vlx_frontend` bind port

| Variable | Description |
| :--- | :--- |
| `FRAMEFLOW_USER` | The dedicated, unprivileged user that runs the background services (default: `frameflow`). |
| `MLVPN_SERVER_IP` | The remote server IP for MLVPN (UDP traffic). |
| `MLVPN_SLOT` | MLVPN tunnel identity (client). Must match this client's peer slot on the server. |
| `MLVPN_CLIENT_TUN_IP` | Optional override for client TUN IP. |
| `MLVPN_SERVER_TUN_IP` | Optional override for gateway/server TUN IP. |
| `MLVPN_REMOTE_PORT` | Optional override for remote UDP port. |
| `SHADOWSOCKS_SERVER_IPS` | The remote server IP(s) for Shadowsocks bonding (TCP proxy traffic). |
| `AP_PASSWORD` | WPA2 passphrase for the generated Access Point. Automatically generated if left empty. |
| `MPTCP_PROXY_PASS` | Password for the MPTCP proxy. |
| `MLVPN_KEY` | Key for the single-client MLVPN tunnel. |
| `DB_DSN` | SQLite database DSN for cameraman configuration (default: `/opt/VLX_FrameFlow/var/frameflow.db`). |
| `CAM_PATH_PREFIX` | Remote MediaMTX path prefix (default: `cameraman`). |
| `CAM_MAX_RESOLUTION` | Maximum fallback video resolution for Cameraman (default: `1920x1080`). |
| `CAM_MAX_FPS` | Maximum fallback video FPS for Cameraman (default: `30`). |
| `bind_address` | Backend API bind address (default: `127.0.0.1`). |
| `bind_port` | Backend API bind port (default: `9090`). |
| `allowed_origins` | Comma-separated list of allowed CORS origins for the API. |
| `bkend_user1` / `bkend_pass1` | First tier of backend API credentials. |
| `backend_address` | Target backend address for the Svelte frontend (default: `127.0.0.1`). |
| `backend_port` | Target backend port for the Svelte frontend (default: `9090`). |
| `FF_GUI_USER` / `FF_GUI_PASS` | Credentials used to authenticate to the Svelte Control Panel frontend. |
| `client_crt` / `client_key` | Optional zero-trust mTLS paths for Svelte Control Panel to authenticate against the backend. |
| `relay_client_host` | The remote Client API IP accessed via MLVPN tunnel (default: `10.1.10.2`). |
| `relay_client_port` | The remote Client API Port accessed via MLVPN tunnel (default: `9090`). |
| `use_relay` | For Frontend settings: `true` routes all UI module commands through the relay (`api/v1/relay/...`), useful when hosting UI on Server. `false` targets local Client API. |
Expand All @@ -252,11 +271,10 @@ Note: Adjust the port (`8080` by default) to match your `vlx_frontend` bind port

### Streaming Endpoints

Both `RTSP_URL` and `SRT_URL` endpoints undergo **Strict URL Validation** before they are actively bound to the ingest pipeline. A malformed URL configuration (e.g., missing scheme) will proactively prevent the FFmpeg streaming unit from attempting to start, and instead will yield a direct descriptive error in the log.
Both endpoints undergo **Strict URL Validation** before they are actively bound to the ingest pipeline. A malformed URL configuration (e.g., missing scheme) will proactively prevent the FFmpeg streaming unit from attempting to start, and instead will yield a direct descriptive error in the log.

| Variable | Description |
| :--- | :--- |
| `RTSP_URL` | Base RTSP URL, primarily used for **Standard Ingest** on internal networks. |
| `SRT_URL` | Base SRT URL, explicitly reserved for **Bonded Ingest** over the MLVPN tunnel. Safely auto-parses `publish:` StreamID credentials (`user:pass`). |

**Example:**
Expand All @@ -269,8 +287,7 @@ srt://10.1.10.1:8890?streamid=publish:stream_name:user:pass
| Variable | Description |
| :--- | :--- |
| `GPSPORT` | gpsd local port (default: `1198`). |
| `API_URL` | Remote HTTP/HTTPS telemetry endpoint. |
| `AUTH_TOKEN` | Bearer token for API authentication. |
| `gps_target_url` | Remote HTTP/HTTPS telemetry endpoint. |

---

Expand Down
13 changes: 11 additions & 2 deletions config/frameflow.settings.template
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ MEDIAMTX_DIR="/opt/mediamtx"
AP_PASSWORD=""

# GPS Tracker
# gpsd local port (default: 1198)
GPSPORT="1198"
# Remote HTTP/HTTPS telemetry endpoint for pushing GPS data
gps_target_url=""

# Network settings
# Specifies role-aware privilege scoping. Do not manually force unless required.
FRAMEFLOW_ROLE=""
# The remote server IP for MLVPN (UDP traffic).
MLVPN_SERVER_IP=""
# MLVPN tunnel identity (client). Must match this client's peer slot on the
# server (see the server's etc/peers.yaml). Slot k derives:
Expand All @@ -29,9 +33,11 @@ MLVPN_SLOT="0"
MLVPN_CLIENT_TUN_IP=""
MLVPN_SERVER_TUN_IP=""
MLVPN_REMOTE_PORT=""
# Supports native Dual-Stack TCP bonding (comma-separated IPv4, IPv6)
# The remote server IP(s) for Shadowsocks bonding (TCP proxy traffic). Supports native Dual-Stack TCP bonding (comma-separated IPv4, IPv6)
SHADOWSOCKS_SERVER_IPS=""
# Password for the MPTCP proxy.
MPTCP_PROXY_PASS=""
# Key for the single-client MLVPN tunnel.
MLVPN_KEY=""

# Cameraman
Expand All @@ -51,11 +57,14 @@ CAM_MAX_FPS="30"
# Client API
# This is the native backend API on the Client device (SBC).
# It serves local UI requests and remote requests routed through the Server API relay.
# Backend API bind address (default: 127.0.0.1).
bind_address=127.0.0.1
# Backend API bind port (default: 9090).
bind_port=9090
# (comma-separated list of allowed CORS origins)
# Comma-separated list of allowed CORS origins for the API.
allowed_origins="127.0.0.1"

# Backend Credentials
# First tier of backend API credentials.
bkend_user1=admin
bkend_pass1=admin
2 changes: 2 additions & 0 deletions config/frameflow_srv.settings.template
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ MEDIAMTX_DIR="/opt/mediamtx"
# Explicitly sets the system to require root execution for main orchestrator daemons,
# while utilizing /etc/systemd/system/ and multi-user.target for unit files.
FRAMEFLOW_ROLE="SERVER"
# Password for the MPTCP proxy.
MPTCP_PROXY_PASS=""
# Key for the single-client MLVPN tunnel.
# MLVPN_KEY is used only for the legacy single-client tunnel (no peers.yaml).
# For multiple clients, define each one in etc/peers.yaml (see peers.yaml.template);
# when that file is present it takes precedence and each peer carries its own key.
Expand Down
40 changes: 22 additions & 18 deletions internal/sysutils/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,26 +166,30 @@ func InstallBinary(isServer bool) error {
}
Info("Binary successfully installed to %s", targetPath)

// Explicitly copy required client binaries
// Explicitly copy required binaries (including frontend for both roles)
srcDir := filepath.Dir(exePath)
if !isServer {
requiredBinaries := []string{"VLX_FrameFlow", "vlx_frontend"}
for _, bin := range requiredBinaries {
binPath := filepath.Join(srcDir, bin)
if _, err := os.Stat(binPath); err == nil {
var requiredBinaries []string
if isServer {
requiredBinaries = []string{"vlx_frontend"}
} else {
requiredBinaries = []string{"VLX_FrameFlow", "vlx_frontend"}
}

for _, bin := range requiredBinaries {
binPath := filepath.Join(srcDir, bin)
if _, err := os.Stat(binPath); err == nil {
tgt := filepath.Join(installTargetDir, bin)
if err := copyFile(binPath, tgt, 0755); err == nil {
Info("Required binary %s explicitly copied to %s", bin, tgt)
}
} else {
// Try with arch suffix for the current OS/Arch if raw name not found
sfx := "_" + runtime.GOARCH
sfxPath := filepath.Join(srcDir, bin+sfx)
if _, err := os.Stat(sfxPath); err == nil {
tgt := filepath.Join(installTargetDir, bin)
if err := copyFile(binPath, tgt, 0755); err == nil {
Info("Required binary %s explicitly copied to %s", bin, tgt)
}
} else {
// Try with arch suffix for the current OS/Arch if raw name not found
sfx := "_" + runtime.GOARCH
sfxPath := filepath.Join(srcDir, bin+sfx)
if _, err := os.Stat(sfxPath); err == nil {
tgt := filepath.Join(installTargetDir, bin)
if err := copyFile(sfxPath, tgt, 0755); err == nil {
Info("Required binary %s (from %s) explicitly copied to %s", bin, bin+sfx, tgt)
}
if err := copyFile(sfxPath, tgt, 0755); err == nil {
Info("Required binary %s (from %s) explicitly copied to %s", bin, bin+sfx, tgt)
}
}
}
Expand Down
Loading