From 5a662b61660edf3386f3b2b6be8d42f9059fd224 Mon Sep 17 00:00:00 2001
From: viruslox <12681439+viruslox@users.noreply.github.com>
Date: Fri, 21 Aug 2026 11:51:26 +0000
Subject: [PATCH] chore: update documentation and installation scripts
- Updated README.md and ARCHITECTURE.md with missing configurations (`FRAMEFLOW_ROLE`, `peers.yaml`).
- Added Apache Reverse Proxy setup instructions in README.md.
- Modified `internal/sysutils/install.go` to explicitly copy `peers.yaml.template` and `frontend.settings.template` for both Client and Server roles.
---
ARCHITECTURE.md | 10 +++++++++-
README.md | 35 ++++++++++++++++++++++++-----------
internal/sysutils/install.go | 31 ++++++++++++++++++++-----------
3 files changed, 53 insertions(+), 23 deletions(-)
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index caec7dc..f733396 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -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()`.
diff --git a/README.md b/README.md
index 14ccb67..33c083e 100644
--- a/README.md
+++ b/README.md
@@ -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
-
- ProxyPass http://127.0.0.1:8080/
- ProxyPassReverse http://127.0.0.1:8080/
-
-
-
- ProxyPass ws://127.0.0.1:8080/ws
- ProxyPassReverse ws://127.0.0.1:8080/ws
-
+# ===== FrameFlow peer (frontend : — telemetry WS at /ws) =====
+RedirectMatch ^/frameflow$ /frameflow/
+
+ProxyPass /frameflow/ws ws://127.0.0.1:/ws
+ProxyPass /frameflow/ http://127.0.0.1:/
+ProxyPassReverse /frameflow/ http://127.0.0.1:/
```
-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)://:/frameflow/peer/`
+
+Note: Adjust the `` (`8080` by default) to match your `vlx_frontend` bind port.
### General Paths
| Variable | Description |
@@ -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. |
@@ -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.
diff --git a/internal/sysutils/install.go b/internal/sysutils/install.go
index 4811bf4..765dac5 100644
--- a/internal/sysutils/install.go
+++ b/internal/sysutils/install.go
@@ -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)
}
}
@@ -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
}
}