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
10 changes: 9 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,17 @@ To secure the connection between the remote `vlx_frontend` and the SBC's `VLX_Fr
2. The Client provisions a signed `.p12` or `.pem` Client Certificate for authorized remote UI instances.
3. The core API uses `tls.RequireAndVerifyClientCert`, refusing any connection not signed by the local CA.

### Multi-Client Deterministic Slots and Configuration Roles

The suite supports multi-client MLVPN tunneling utilizing a deterministic slot model configured via `/opt/VLX_FrameFlow/etc/peers.yaml` on the server.
- Each client is assigned an integer slot in `peers.yaml`. This slot deterministically derives the tunnel interface (`mlvpn{slot}`), UDP port (`5080+slot`), and the inner tunnel IP subnet (`10.1.{10+slot}.x`).
- To enforce routing, peer names defined in `peers.yaml` must be lowercase and DNS-label safe.

The daemon resolves architecture nuances (e.g. AMD64 vs ARM64) automatically. Furthermore, the role configuration (`FRAMEFLOW_ROLE` in settings, empty for Client, `SERVER` for Server) dynamically instructs the daemon on which privilege scoping and systemd management mechanisms (e.g., system vs user) should be utilized.

### "Build as User, Run as Root"

The suite enforces a strict dichotomy between Client and Server roles regarding execution privilege and systemd architecture:
The suite enforces a strict dichotomy between Client and Server roles regarding execution privilege and systemd architecture (guided by the `FRAMEFLOW_ROLE` variable):

1. **Build:** Unprivileged compilation via `build.sh`.
2. **Install:** Executing the compiled binary as root triggers `internal/sysutils.InstallBinary()`.
Expand Down
35 changes: 24 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,20 +214,20 @@ This file contains **all runtime environment variables**. It is generated during

### Apache Reverse Proxy

To serve the Control Panel Frontend behind an Apache Reverse Proxy (for example, under the `/vlx/` path), ensure that the `proxy`, `proxy_http`, and `proxy_wstunnel` modules are enabled. Use the following configuration block:
To serve the Control Panel Frontend behind an Apache Reverse Proxy (for example, under the `/frameflow/` path), ensure that the `proxy`, `proxy_http`, and `proxy_wstunnel` modules are enabled. Use the following configuration block:

```apache
<Location /vlx/>
ProxyPass http://127.0.0.1:8080/
ProxyPassReverse http://127.0.0.1:8080/
</Location>

<Location /vlx/ws>
ProxyPass ws://127.0.0.1:8080/ws
ProxyPassReverse ws://127.0.0.1:8080/ws
</Location>
# ===== FrameFlow peer (frontend :<port> — telemetry WS at /ws) =====
RedirectMatch ^/frameflow$ /frameflow/

ProxyPass /frameflow/ws ws://127.0.0.1:<port>/ws
ProxyPass /frameflow/ http://127.0.0.1:<port>/
ProxyPassReverse /frameflow/ http://127.0.0.1:<port>/
```
Note: Adjust the port (`8080` by default) to match your `vlx_frontend` bind port.

And very important: frameflow peers GUI are at address: `http(s)://<apache address>:<port>/frameflow/peer/`

Note: Adjust the `<port>` (`8080` by default) to match your `vlx_frontend` bind port.
### General Paths

| Variable | Description |
Expand All @@ -239,6 +239,7 @@ Note: Adjust the port (`8080` by default) to match your `vlx_frontend` bind port

| Variable | Description |
| :--- | :--- |
| `FRAMEFLOW_ROLE` | Specifies role-aware privilege scoping (`SERVER` or empty for client). |
| `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. |
Expand Down Expand Up @@ -269,6 +270,18 @@ Note: Adjust the port (`8080` by default) to match your `vlx_frontend` bind port

**Note on Server Configuration:** The Server role configuration (`frameflow_srv.settings.template`) intentionally omits these IP variables. The Server is a destination node that securely binds to `0.0.0.0` and `::` locally, and therefore only requires the corresponding authentication keys (`MLVPN_KEY`, `MPTCP_PROXY_PASS`).

### `/opt/VLX_FrameFlow/etc/peers.yaml` (Server Only)

This file is used to configure multi-client MLVPN tunneling using a deterministic slot model. Each client is assigned an integer slot that dictates its tunnel interface (`mlvpn{slot}`), UDP port (`5080+slot`), and tunnel IP subnet (`10.1.{10+slot}.x`). Peer names must be lowercase and DNS-label safe.

```yaml
peers:
# Example peer
- name: client01
slot: 1
key: "YOUR-SECRET-KEY-1"
```

### Streaming Endpoints

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.
Expand Down
31 changes: 20 additions & 11 deletions internal/sysutils/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,15 @@ func InstallBinary(isServer bool) error {
}
}

if !isServer {
// Copy frontend template user settings if it exists
frontendTemplate := filepath.Join(searchPath, "frontend.settings.template")
frontendTarget := filepath.Join(installConfigDir, "frontend.settings")
if _, err := os.Stat(frontendTemplate); err == nil {
if _, err := os.Stat(frontendTarget); os.IsNotExist(err) {
copyFile(frontendTemplate, frontendTarget, 0644)
Info("Copied frontend.settings.template to etc")
} else {
mergeConfigs(frontendTemplate, frontendTarget)
}
// Copy frontend template user settings if it exists
frontendTemplate := filepath.Join(searchPath, "frontend.settings.template")
frontendTarget := filepath.Join(installConfigDir, "frontend.settings")
if _, err := os.Stat(frontendTemplate); err == nil {
if _, err := os.Stat(frontendTarget); os.IsNotExist(err) {
copyFile(frontendTemplate, frontendTarget, 0644)
Info("Copied frontend.settings.template to etc")
} else {
mergeConfigs(frontendTemplate, frontendTarget)
}
}

Expand All @@ -249,6 +247,17 @@ func InstallBinary(isServer bool) error {
mergeConfigs(mtxConfig, mtxTarget)
}
}

// Copy peers.yaml.template to etc/peers.yaml if it exists
peersTemplate := filepath.Join(searchPath, "peers.yaml.template")
peersTarget := filepath.Join(installConfigDir, "peers.yaml")
if _, err := os.Stat(peersTemplate); err == nil {
if _, err := os.Stat(peersTarget); os.IsNotExist(err) {
copyFile(peersTemplate, peersTarget, 0644)
Info("Copied peers.yaml.template to etc")
}
}

break
}
}
Expand Down
Loading