docs(i18n): 同步 24 篇旧英文文档与中文最新内容

guide 9 篇(quick-start/first-site/sso/troubleshooting/tunnel-usage/waf-usage/waf-ip-group-expr/credits/index)、deployment 7 篇(deployment/server/agent/relay/openflared/upgrade/index)、reference 3 篇(configuration/cli/index)、design 5 篇(architecture/agent-design/tunnel-design/waf-design/index)全部按中文最新版重写同步;waf-usage/waf-design 按新版 DAG 模型重写;修复 reference 中文锚点链接;vitepress 构建 43 个英文页面全绿
This commit is contained in:
ryan
2026-08-16 23:27:18 +08:00
parent 454542c1d0
commit e7b8fb2f99
24 changed files with 1985 additions and 2361 deletions
+72 -89
View File
@@ -1,26 +1,38 @@
# Access Agent
You will learn: The responsibilities of the Agent, the difference between the two access Tokens, installation script parameters, `agent.json` settings, and how to verify that the node has successfully connected.
You will learn: the Agent's responsibilities, the difference between the two access Tokens, install script parameters, `agent.json` config, and how to confirm the node is online.
The OpenFlare Agent runs on the proxy node. It does not receive arbitrary remote shell commands; instead, it pulls the configuration version published by the control plane via the Agent API, writes files for OpenResty locally, executes configuration validation, reloads, and attempts to roll back to a working configuration if it fails.
The OpenFlare Agent runs on the proxy node side. It doesn't accept remote shell commands; instead it pulls released config versions from the control plane via the Agent API, writes OpenResty files locally, runs config validation, reloads, and attempts to roll back to a runnable config on failure.
## Connection Credentials
## Connection Methods
| Method | Applicable Scenario |
| Method | Use Case |
| --- | --- |
| `discovery_token` | Automatically registers a node for the first time, which the Server exchanges for a node-specific credential |
| `agent_token` | Node has already been created/allocated in the management console, directly uses this node-specific credential |
| `discovery_token` | first-time auto-registration; the Server exchanges it for a node-specific credential |
| `agent_token` | node already created/assigned in the admin panel; connect with the node-specific credential |
At least one of `agent_token` or `discovery_token` must be configured.
At least one of `agent_token` / `discovery_token` is required.
### Credential Retrieval Path
### Credential Paths
- **`discovery_token` (Auto Registration Token)**: Log into the management console, navigate to "System Settings" -> "Auto Registration", where you can generate, view, and copy the global auto-registration credential.
- **`agent_token` (Node Specific Token)**: Log into the management console, navigate to "Node Management" -> "Add Node", fill in basic node information, save, and copy the node-specific access Token in the node details.
- **`discovery_token` (auto-registration credential)**: log in to the admin panel, navigate to「System Settings」->「Auto Registration」; generate, view, and copy the global auto-registration credential there.
- **`agent_token` (node-specific credential)**: log in to the admin panel, navigate to「Node Management」->「Add Node」; after filling in basic info and saving, copy the node-specific access Token on the node detail page.
## One-Click Installation
## One-Click Install
Using the `discovery_token`:
### Interactive Install (recommended)
Running the install script without any arguments enters interactive mode, with a wizard choosing the install method (local / Docker container) and configuring the Server address and auth Token (if Docker is chosen and Docker isn't installed locally, the script asks and intelligently installs Docker):
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash
```
### Automated (non-interactive) Install
Adding any arguments enters automated install mode with no interaction.
Local install with `discovery_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
@@ -28,7 +40,7 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst
--discovery-token YOUR_DISCOVERY_TOKEN
```
Using the node-specific `agent_token`:
Local install with node-specific `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
@@ -36,29 +48,40 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst
--agent-token YOUR_AGENT_TOKEN
```
The installation script downloads the latest Agent, writes to `/opt/openflare-agent` by default, generates `agent.json`, and registers `openflare-agent.service` on Linux + systemd environments.
Automated Docker container install:
Supported arguments:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--discovery-token YOUR_DISCOVERY_TOKEN \
--docker
```
| Argument | Description | Default Value |
| --- | --- | --- |
| `--server-url` | Server address (required) | |
| `--discovery-token` | One-time auto-registration Token | |
| `--agent-token` | Node-specific Token | |
| `--install-dir` | Target installation directory | `/opt/openflare-agent` |
| `--openresty-path` | Path to the OpenResty binary; automatically detects `openresty` if unspecified | |
| `--repo` | GitHub repository to download from | `Rain-kl/OpenFlare` |
| `--no-service` | Do not register systemd service | |
In local install mode, the script downloads the latest Agent, writes to `/opt/openflare-agent` by default, generates `agent.json`, auto-detects and creates the low-privilege system account `openflare` (granting the whole install dir to it), and creates the `openflare-agent.service` systemd service on Linux + systemd. The service runs as the `openflare` unprivileged user, with Linux Capabilities (`CAP_NET_BIND_SERVICE`) enabling privileged ports (e.g. 80, 443).
## Configuration File
Supported parameters:
Default configuration file path:
| Parameter | Description |
| --- | --- |
| `--server-url` | Server address |
| `--discovery-token` | first-time auto-registration Token |
| `--agent-token` | node-specific Token |
| `--install-dir` | install dir, default `/opt/openflare-agent` (local install only) |
| `--openresty-path` | OpenResty binary path; auto-finds `openresty` when omitted (local install only) |
| `--repo` | GitHub repo for downloading the Agent, default `Rain-kl/OpenFlare` |
| `--no-service` | don't create the systemd service (local install only) |
| `--docker` | install via Docker container |
| `--method` | install method: `local` or `docker` (default `local`) |
## Config File
Default config file path:
```text
/opt/openflare-agent/agent.json
```
Example local configuration:
Local config example:
```json
{
@@ -67,13 +90,13 @@ Example local configuration:
"data_dir": "./data",
"openresty_path": "openresty",
"openresty_observability_port": 18081,
"observability_replay_minutes": 15,
"heartbeat_interval": 10000,
"observability_replay_minutes": 60,
"heartbeat_interval": 3000,
"request_timeout": 10000
}
```
Example customized OpenResty paths configuration:
Custom OpenResty path example:
```json
{
@@ -87,90 +110,50 @@ Example customized OpenResty paths configuration:
"cert_dir": "/var/lib/openflare-agent/etc/nginx/certs",
"lua_dir": "/var/lib/openflare-agent/etc/nginx/lua",
"runtime_config_dir": "/var/lib/openflare-agent/etc/openflare",
"heartbeat_interval": 10000,
"heartbeat_interval": 3000,
"request_timeout": 10000
}
```
If `openresty_path` is not configured, the Agent calls `openresty` by default. For the full fields, see [Configurations Reference](../reference/configuration.md#agent-configurations-fields).
Without `openresty_path`, the Agent calls `openresty` by default. Full fields: [Configuration Reference](../reference/configuration.md#agent-命令行参数与配置字段).
## Running in Docker
## Running with Docker
For Docker deployments, run the Agent image containing built-in OpenResty directly:
For Docker deployment, directly run the Agent image with a built-in OpenResty:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443 \
-p 80:80 -p 443:443/tcp -p 443:443/udp \
-v openflare-agent-pages:/data/var/lib/openflare/pages \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
## Start & Validate
In a systemd environment:
```bash
systemctl start openflare-agent
systemctl status openflare-agent
journalctl -u openflare-agent -f
```
Manual execution:
```bash
/opt/openflare-agent/openflare-agent -config /opt/openflare-agent/agent.json
```
Running from source:
```bash
cd openflare-agent
export LOG_LEVEL='info'
go run ./cmd/agent -config /path/to/agent.json
```
Running compiled binary:
```bash
cd openflare-agent
go build -o openflare-agent ./cmd/agent
export LOG_LEVEL='info'
./openflare-agent -config /path/to/agent.json
```
Confirm in the management console:
| Position | Expected Result |
| --- | --- |
| Node List | Node status is online |
| Node Details | Heartbeat, current version, and basic resource metrics display correctly |
| Apply Logs | Application result displays after publishing |
> [!NOTE]
> **Pages persistence**
> By default the Pages deployment dir is mounted to the Docker named volume `openflare-agent-pages` (container path `/data/var/lib/openflare/pages`). Rebuilding or upgrading the Agent container doesn't require re-pulling static site packages.
## Uninstall
To completely uninstall the Agent and wipe local data:
### Interactive Uninstall (recommended)
Running the uninstall script without any arguments enters interactive mode with a menu choosing the method (local uninstall / Docker container uninstall):
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
Supported arguments:
### Docker Container Uninstall
| Argument | Description | Default Value |
| --- | --- | --- |
| `--install-dir` | Installation directory | `/opt/openflare-agent` |
| `--service-name` | systemd service name | `openflare-agent` |
Stop and remove the `openflare-agent` container.
The uninstallation script only removes the Agent service, processes, and installation directory; it does not uninstall OpenResty from the host.
## FAQ
## Common Questions
| Symptom | Actions |
| Symptom | Handling |
| --- | --- |
| `agent_token and discovery_token cannot both be empty` | Check if at least one Token is configured in `agent.json` |
| Node stays offline | Run `curl -I http://your-server:3000` on the Agent node to verify that the Server is reachable |
| OpenResty is not running | Review `journalctl -u openflare-agent`, checking that `openresty_path` is executable and ports 80/443 are not bound |
| Repeated application failures after publishing | The Agent blocks repeated sync attempts of the same failing `version + checksum`; fix the configuration and republish, or activate an older version to roll back |
| `agent_token and discovery_token cannot both be empty` | check that `agent.json` has at least one Token |
| Node stays offline | run `curl -I http://your-server:3000` on the Agent node to confirm the Server address is reachable |
| Repeated failure after release | the Agent blocks re-applying the same `version + checksum`; click「Force Sync」in the node detail page, or republish a new version |
+49 -185
View File
@@ -1,8 +1,8 @@
# Deployment Guide
You will learn: The recommended deployment strategies for OpenFlare, the system requirements for Server and Agent, how to run from source, integration steps, upgrades, and uninstallation entrypoints.
You will learn: OpenFlare's recommended deployment approaches, Server and Agent runtime requirements, source startup, integration steps, and upgrade/uninstall entries.
In production environments, we highly recommend using PostgreSQL as the Server database and explicitly configuring `SESSION_SECRET` for the Server. The recommended Agent deployment method is Docker (which runs the Agent image containing built-in OpenResty); host systemd service installation via script and manual local run are also supported.
Production recommends PostgreSQL as the Server DB, with `APP_SESSION_SECRET` etc. configured via `config.yaml` or env vars. The full Docker Compose deployment requires Redis; ClickHouse is optional for massive access logs and observability time series (see the repo root `docker-compose.yaml`). The Agent supports both Docker deployment and a local install script; the Docker image bundles the OpenResty binary. Log-DB determination and switching: [Log Store Decoupling](../design/logstore.md).
## Deployment Topology
@@ -31,15 +31,15 @@ Origin service
Browser
|
v
OpenResty (Agent, WAF/HTTPS Termination) <-- TunnelRelay Node
OpenResty (Agent, WAF/HTTPS termination) <-- TunnelRelay node
|
| proxy_pass (127.0.0.1:{vhost_port})
v
OpenFlareRelay (frps process) <-- TunnelRelay Node
OpenFlareRelay (frps process) <-- TunnelRelay node
|
| frp tunnel protocol
v
OpenFlared (frpc client) <-- Intranet Server
OpenFlared (frpc client) <-- intranet server
|
v
Internal Service (192.168.x.x)
@@ -47,150 +47,75 @@ Internal Service (192.168.x.x)
## Prerequisites
Server:
### Hardware Recommendations
| Item | Requirement |
| --- | --- |
| Go | `1.25+`, required only when running from source |
| Node.js | `18+`, required only when building the admin frontend from source |
| Database | Writable SQLite parent directory, or a reachable PostgreSQL instance |
| Port | Listens on port `3000` by default |
Agent:
| Item | Requirement |
| --- | --- |
| System | The installation script supports Linux and macOS; the systemd service is created only on Linux + systemd environments |
| Architecture | `amd64` or `arm64` |
| OpenResty | Required to have the `openresty` executable when deploying locally, or specify its path via `--openresty-path` |
| Docker | Required only when deploying the Agent via Docker image |
| Network | The Agent node must be able to reach the Server address |
| GeoIP | WAF regional rules rely on the Agent's local MaxMind mmdb; the Agent initializes a built-in library on startup and updates it periodically |
### Hardware Allocation Recommendations
| Component | Minimum Allocation | Recommended Allocation | Note |
| Component | Reference (entry) | Reference (production) | Notes |
| --- | --- | --- | --- |
| **Server Control Plane** | 1 Core CPU / 1 GB RAM / 10 GB Disk | 2 Cores CPU / 4 GB RAM / 50 GB+ Disk | Expand disk allocation according to log retention windows and concurrency. |
| **Agent Data Plane** | 1 Core CPU / 512 MB RAM / 2 GB Disk | 2 Cores CPU / 2 GB RAM / 10 GB+ Disk | Expand according to concurrent reverse proxy connections and WAF workloads. |
| **Relay Node** | 1 Core CPU / 1 GB RAM / 5 GB Disk | 2 Cores CPU / 2 GB RAM / 20 GB Disk | frps throughput is primarily bounded by CPU processing capacity and bandwidth. |
| **OpenFlared Client** | 1 Core CPU / 256 MB RAM / 1 GB Disk | 1 Core CPU / 512 MB RAM / 5 GB Disk | Runs inside the intranet; utilizes minimal CPU/RAM, optimize for network throughput. |
| **Server control plane** | 1 core / 2 GB RAM / 20 GB disk | 2 cores / 4 GB RAM / 50 GB+ disk | expand disk by access-log retention and concurrent traffic |
| **Agent data plane** | 1 core / 512 MB RAM / 2 GB disk | 2 cores / 2 GB RAM / 10 GB+ disk | expand by OpenResty concurrent proxy connections and WAF interception |
| **Relay node** | 1 core / 1 GB RAM / 5 GB disk | 2 cores / 2 GB RAM / 20 GB disk | frps relay throughput limited by bandwidth and CPU |
| **OpenFlared client** | 1 core / 256 MB RAM / 1 GB disk | 1 core / 512 MB RAM / 5 GB disk | runs independently in the intranet, tiny footprint |
## Docker Compose Deployment for Server
## Docker Compose Server Deployment
Create a `docker-compose.yml` file:
```yaml
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
container_name: openflare
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-a-long-random-string
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
volumes:
- openflare-data:/data
volumes:
postgres-data:
openflare-data:
```
Start the Server:
The repo root provides a full `docker-compose.yaml` (PostgreSQL, Redis, ClickHouse, Jaeger).
```bash
curl -o .env.example https://raw.githubusercontent.com/Rain-kl/OpenFlare/refs/heads/main/.env.example
cp .env.example .env
# edit .env; at minimum change APP_SESSION_SECRET and the DB passwords
docker compose up -d
docker compose ps
docker compose logs -f openflare
```
Access `http://localhost:3000` for the first time, using the default credentials `root` / `123456`. Please change the default password immediately after logging in.
First visit `http://localhost:3000`; default account `admin` / `12345678`. Change the default password immediately after login.
## Start Server from Source
## Source Startup
First, build the admin frontend:
First build the admin frontend:
```bash
cd openflare-server/web
cd frontend
corepack enable
pnpm install
pnpm build
pnpm build:embed
```
Then, launch the Server:
Then start the Server (repo root):
```bash
cd openflare-server
export SESSION_SECRET='replace-with-a-long-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
# Optional: Prefer PostgreSQL by setting DSN
# export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
go run .
cp config.example.yaml config.yaml
export APP_SESSION_SECRET='replace-with-a-long-random-string'
# optional: use PostgreSQL
# export DB_HOST=127.0.0.1 DB_USERNAME=postgres DB_PASSWORD=postgres DB_NAME=openflare
go run main.go all
```
By default, the Server listens on port `3000`. You can also specify it explicitly:
Listens on `:3000` by default (controlled by `app.addr` in `config.yaml` or `APP_ADDR`).
```bash
go run . --port 3000 --log-dir ./logs
```
## Run the Agent with Docker (recommended)
## Running Agent in Docker (Recommended)
Docker is the recommended deployment method for the Agent. Running the Agent image directly launches the Agent controller alongside the built-in OpenResty binary. If `node_ip` is left blank, the Agent automatically resolves its outbound public IP via third-party APIs, avoiding registering the Docker bridge address as the node IP.
Mounting the configuration file:
Docker is the recommended Agent deployment. The Agent image is built on the OpenResty image, bundling the Agent controller and the OpenResty binary. Without an explicit `node_ip`, the Agent prefers fetching the real egress IP via a third-party API, avoiding registering the Docker bridge address as the node IP.
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443 \
-v openflare-agent-data:/data \
-v ./agent.json:/etc/openflare/agent.json:ro \
ghcr.io/rain-kl/openflare-agent:latest
```
Using environment variables:
```bash
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443 \
-p 80:80 -p 443:443/tcp -p 443:443/udp \
-v openflare-agent-pages:/data/var/lib/openflare/pages \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
## Agent Connection via Installation Script
The named volume `openflare-agent-pages` persists the Pages deployment dir; rebuilding the container doesn't require re-pulling static site packages.
Apart from Docker, you can deploy the Agent directly on a Linux/macOS host using the installation script.
## Agent Access (script install)
Auto-register using `discovery_token`:
Besides Docker, the install script can deploy the Agent to the local host. The script registers the low-privilege `openflare` service account and runs the systemd service as that user, using Linux Capabilities to safely listen on privileged ports 80/443.
Auto-register with `discovery_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
@@ -198,7 +123,7 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst
--discovery-token YOUR_DISCOVERY_TOKEN
```
Connect using node-specific `agent_token`:
With node-specific `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
@@ -206,82 +131,21 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst
--agent-token YOUR_AGENT_TOKEN
```
Installation script arguments:
Install script parameters:
| Argument | Description | Default Value |
| --- | --- | --- |
| `--server-url` | Server address (required) | |
| `--discovery-token` | Auto-registration Token; mutually exclusive with `--agent-token` | |
| `--agent-token` | Node-specific Token; mutually exclusive with `--discovery-token` | |
| `--install-dir` | Target installation directory | `/opt/openflare-agent` |
| `--openresty-path` | Path to the OpenResty binary; automatically detects `openresty` if unspecified | |
| `--repo` | GitHub repository to download from | `Rain-kl/OpenFlare` |
| `--no-service` | Do not register systemd service | |
| Parameter | Description |
| --- | --- |
| `--server-url` | Server address, required |
| `--discovery-token` | first-time auto-registration Token, one of two with `--agent-token` |
| `--agent-token` | node-specific Token, one of two with `--discovery-token` |
| `--install-dir` | install dir, default `/opt/openflare-agent` |
| `--openresty-path` | OpenResty binary path; auto-finds `openresty` when omitted |
| `--repo` | GitHub repo for downloading the Agent, default `Rain-kl/OpenFlare` |
| `--no-service` | don't create the systemd service |
Confirm service status:
Confirm state:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -f
```
## Running the Agent Manually
Running from source:
```bash
cd openflare-agent
export LOG_LEVEL='info'
go run ./cmd/agent -config /path/to/agent.json
```
Running compiled binary:
```bash
cd openflare-agent
go build -o openflare-agent ./cmd/agent
export LOG_LEVEL='info'
./openflare-agent -config /path/to/agent.json
```
Minimal `agent.json` example:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "./data",
"openresty_path": "openresty",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
If `openresty_path` is left blank, the Agent calls `openresty` by default.
By default, the Agent attempts to upgrade the HTTP heartbeat connection to WebSocket once successfully registered. Once upgraded, configuration activations on the Server notify the Agent instantly; if WebSocket disconnects or fails to establish, the Agent gracefully falls back to HTTP polling.
WAF geographical filtering depends on the local `GeoLite2-Country.mmdb`. The Agent automatically writes the built-in database to `data_dir/etc/openflare/GeoLite2-Country.mmdb` on startup and checks for periodic updates. Muted warnings are logged if updates fail, having no impact on Nginx configuration sync or reloads.
## Upgrades & Uninstallation
Server:
* Root users can check and trigger Server upgrades in the top header of the management console.
* To deploy preview releases, manually check the GitHub Releases page.
* You can also trigger upgrades by uploading the compiled Server binary in the console.
Agent:
* By default, the Agent automatically upgrades following stable releases.
* Agent self-updates require the GitHub Release to contain the compiled binary and a matching `.sha256` checksum file; updates are blocked if the downloaded binary fails the SHA-256 validation.
* You can re-execute the installation script to redeploy or force-update the Agent.
* Upgrading to preview releases requires a manual trigger.
Uninstalling the Agent:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/uninstall-agent.sh | bash
```
The uninstallation script stops the Agent process, removes the systemd service unit, and wipes the installation directory, without uninstalling OpenResty from the host.
+11 -11
View File
@@ -1,24 +1,24 @@
# Deployment & Upgrade
# Deployment & Upgrades
This section provides detailed deployment guides, configuration instructions, and upgrade maintenance procedures for the OpenFlare Server, Agent, Relay, and the OpenFlared client.
This section provides detailed deployment guides, configuration notes, and upgrade/maintenance steps for the OpenFlare Server, Agent, Relay, and the OpenFlared intranet penetration client.
## Content Navigation
### Quick Start
* **[Quick Start](../guide/quick-start.md)**: Start the Server and your first Agent in under 5 minutes using Docker Compose (recommended for new users).
* **[Quick Start](../guide/quick-start.md)**: start the Server and your first Agent with Docker Compose in 5 minutes (recommended for new users)
### Server Deployment
* **[Launch Server](./server.md)**: Learn how to build the frontend from source, start the Server, and choose between SQLite or PostgreSQL.
* **[Start the Server](./server.md)**: build the frontend from source, start the Server, choose SQLite or PostgreSQL
### Agent Deployment
* **[Deploy Agent](./agent.md)**: Explore Agent connection methods, Docker deployment, host script installation, config files, and troubleshooting.
* **[Access Agent](./agent.md)**: Agent connection methods, Docker deployment, script install, config file, and troubleshooting
### Tunnel Intranet Penetration Deployment
* **[Deploy Relay](./relay.md)**: View config descriptions, Docker deployment, and host runtime guides for TunnelRelay nodes.
* **[Deploy OpenFlared](./openflared.md)**: Access config descriptions, Docker runtime, and auto-sync mechanisms for the intranet client.
* **[Deploy Relay](./relay.md)**: TunnelRelay node config notes, Docker deployment, and host running guide
* **[Deploy OpenFlared](./openflared.md)**: intranet penetration client config notes, Docker running, and self-sync mechanism
### Upgrade & Maintenance
* **[Upgrade & Maintenance](./upgrade.md)**: Discover upgrading procedures for Server/Agent, data retention rules, and validation commands.
### Upgrades & Maintenance
* **[Upgrade & Maintenance](./upgrade.md)**: Server and Agent upgrade steps, data cleanup policy, verification commands
### Reference Manuals
* **[Deployment Guide](./deployment.md)**: Browse deployment topologies, prerequisites, Docker Compose samples, and multiple deployment strategies.
### Reference
* **[Deployment Guide](./deployment.md)**: deployment topology, prerequisites, Docker Compose config examples, and an overview of multiple deployment approaches
+34 -69
View File
@@ -1,42 +1,42 @@
# Deploy OpenFlared Client
# Deploy the OpenFlared Client
You will learn: The responsibilities of the OpenFlared client, configuration parameters and environment variables, how to run the client via Docker, and how to deploy the client on an intranet server using the compiled host binary.
You will learn: OpenFlared's responsibilities, config parameters and env vars, running the client with Docker, and deploying it standalone on an intranet server via binary.
**OpenFlared** is a tunnel client deployed in the user's intranet environment (LANs, private VPCs, or other environments that cannot be directly accessed from the public internet). Its core responsibility is to establish communication with the control plane (OpenFlare Server) via the `X-Tunnel-Token` header, automatically spawning and managing one or more **frpc (Fast Reverse Proxy Client)** subprocesses locally to securely and stably tunnel HTTP traffic back to public relay nodes.
**OpenFlared** is the tunnel client deployed in your intranet (LAN, private cloud, or any environment not directly reachable from the public internet). Its core responsibility is communicating with the control plane (OpenFlare Server) via `X-Tunnel-Token`, and locally spawning and managing one or more **frpc (fast reverse proxy client)** processes to securely and stably tunnel intranet HTTP traffic to public relay nodes.
---
## Prerequisites
1. **Retrieve Tunnel Token**: Create a new tunnel instance on the "Intranet Penetration" or "Tunnel Management" page in the OpenFlare management console; the system will automatically generate a unique `tunnel_id` and a `tunnel_token` (e.g., `tun-<32hex>`).
2. **Outbound Network Permissions**: The intranet server does not require any inbound public IPs or port mappings, but it must be able to reach the **OpenFlare Server URL** and the corresponding **TunnelRelay node control port (default 7000)** over the outbound network.
3. **Software Dependencies** (Host deployment only):
- You must have an executable `frpc` binary locally (recommended version `v0.61.0+` or the latest stable `v0.69.0`), or specify its path explicitly in the configuration.
1. **Get a Tunnel Token**: add a node of type **Tunnel** in admin「Node Management」, save it, then open the node detail page to view the dedicated access Token.
2. **Outbound network access**: the intranet server needs no public inbound IP or port mapping, but must reach the public **OpenFlare Server address** and the corresponding **TunnelRelay node relay port (default 7000)** over the network.
3. **Software dependency** (host deployment only):
- an executable `frpc` binary locally, or an explicitly specified path via parameter.
---
## Configuration & Environment Variables
## Config File and Env Vars
`openflared` reads `flared.json` in the working directory by default on startup. Overriding options via environment variables is fully supported.
`openflared` reads `flared.json` in the current directory by default at startup, fully overridable via env vars.
### Configuration Fields Details
### Config Field Details
| JSON Field | Environment Variable | Description | Default Value |
| JSON field | Env var | Description | Default |
| --- | --- | --- | --- |
| `server_url` | `OPENFLARE_SERVER_URL` | OpenFlare Server API base URL | **None (Required)** |
| `tunnel_token` | `OPENFLARE_TUNNEL_TOKEN` | Tunnel client dedicated access Token | **None (Required)** |
| `frpc_path` | `OPENFLARE_FRPC_PATH` | Path to the `frpc` executable binary | `"frpc"` |
| `data_dir` | `OPENFLARE_DATA_DIR` | Directory to store local data and generated `frpc_{relayNodeID}.toml` configs | `"./data"` |
| `state_path` | - | Path to store local state JSON file (saving the last applied version) | `"{data_dir}/flared-state.json"` |
| `heartbeat_interval`| - | Heartbeat reporting interval (ms or Go Duration string) | `10000` (10s) |
| `sync_interval` | - | Tunnel config polling interval (ms or Go Duration string) | `30000` (30s) |
| `request_timeout` | - | HTTP request timeout duration | `10000` (10s) |
| `server_url` | `OPENFLARE_SERVER_URL` | OpenFlare Server API service address | **none (required)** |
| `tunnel_token` | `OPENFLARE_TUNNEL_TOKEN` | tunnel client's dedicated auth Token | **none (required)** |
| `frpc_path` | `OPENFLARE_FRPC_PATH` | frpc executable binary path | `"frpc"` |
| `data_dir` | `OPENFLARE_DATA_DIR` | local data and generated `frpc_{relayNodeID}.toml` directory | `"./data"` |
| `state_path` | - | local state record file path (last applied config version) | `"{data_dir}/flared-state.json"` |
| `heartbeat_interval`| - | state heartbeat report period (ms int or Go Duration string) | `10000` (10s) |
| `sync_interval` | - | tunnel config pull/sync period (ms int or Go Duration string) | `30000` (30s) |
| `request_timeout` | - | API network request timeout | `10000` (10s) |
---
## Docker Deployment (Recommended)
## Running with Docker
Docker is the simplest and safest way to run the client inside the intranet. The official `openflared` image embeds the client controller and `frpc v0.69.0` out of the box, requiring no environment setup.
Docker deployment is the simplest and safest way to run in the intranet. The official `openflared` image bundles the client controller and the `frpc v0.69.0` binary runtime — no extra environment needed.
```bash
docker pull ghcr.io/rain-kl/openflared:latest
@@ -51,59 +51,24 @@ docker run -d --name openflared --restart unless-stopped \
---
## Manual Host Deployment
## Startup and Verification
If you need to run the client directly on a Linux/macOS/Windows host inside the intranet:
### 1. Auto-Sync Logic
### 1. Compile the Binary
After starting successfully, OpenFlared runs this workflow:
- **Heartbeat & config fetch**: periodically syncs with the Server's `/api/v1/tunnel/heartbeat` and `/api/v1/tunnel/config/active` endpoints, validating the Token and detecting config versions.
- **File rendering**: when a config version (or checksum) changes, it auto-pulls the tunnel's full route rules. If multiple Relay nodes are bound, it renders `frpc_{relayNodeID}.toml` per Relay under `data_dir`.
- **Config-change restart**: when config or checksum changes, it re-spawns the corresponding `frpc` child processes to keep traffic mappings current.
- **Abnormal self-recovery**: if a local `frpc` tunnel process exits abnormally, the supervisor restarts it with exponential backoff (initial 1s, cap 60s).
```bash
cd openflared
go build -o flared ./cmd/flared
```
### 2. Prepare `flared.json`
Create a `flared.json` configuration file in the same directory as the executable:
```json
{
"server_url": "http://your-server-ip:3000",
"tunnel_token": "your-tunnel-auth-token",
"frpc_path": "/usr/local/bin/frpc",
"data_dir": "./data",
"heartbeat_interval": "10s",
"sync_interval": "30s"
}
```
### 3. Start the Service
```bash
export LOG_LEVEL='info'
./flared -config ./flared.json
```
---
## Start & Validate
### 1. Auto-Sync Workflow
Once started successfully, OpenFlared operates the following workflow:
- **Heartbeat & Config Fetching**: Periodically polls `/api/flared/heartbeat` and `/api/flared/config` endpoints to validate the Token and evaluate configuration versions.
- **File Rendering**: When a new configuration version (or checksum mismatch) is detected, it pulls the complete tunnel routing rules. If multiple Relays are bound, it renders `frpc_{relayNodeID}.toml` configurations in `data_dir` for each Relay.
- **Hot Reload or Restart**: Spawns the corresponding `frpc` subprocesses, or executes `frpc reload` / restart actions when configurations change, ensuring traffic mappings are kept up to date.
- **Process Auto-Recovery**: If a local `frpc` tunnel process exits unexpectedly, the master program automatically restarts it after a 5-second backoff penalty.
### 2. View Logs & Connection Status
### 2. View Logs and Connection State
```bash
# Docker container logs
docker logs -f openflared
```
If running correctly, the logs will show output similar to:
If the process runs correctly, you'll see output like:
```text
flared config loaded ...
detected frpc version v0.69.0
@@ -112,8 +77,8 @@ applying new tunnel config {"version": "...", "checksum": "..."}
frpc process missing, starting {"relay_id": "..."}
```
### 3. Verify in the Management Console
### 3. Confirm in the Admin Panel
Open the **"Intranet Penetration"** page in the management console:
- Check the online status of the corresponding tunnel; it should display green as **"Online"**.
- You can inspect which relay nodes the tunnel is connected to, and view the detailed routing configurations of the intranet services.
Open **「Node Management」** in the admin panel and enter the Tunnel node's detail page:
- View the node online state and flared runtime state (WebSocket connected / running / offline).
- View the current applied version and the latest apply record.
+45 -79
View File
@@ -1,48 +1,48 @@
# Deploy Relay (Tunnel Relay)
You will learn: The responsibilities of a TunnelRelay node, `openflare-relay` configuration parameters and environment variables, how to run the Relay via Docker, and how to build and deploy the Relay from source manually.
You will learn: TunnelRelay node responsibilities, `openflare-relay` config items and env vars, running Relay with Docker, and building/deploying Relay manually from source.
In the OpenFlare intranet penetration architecture, the **TunnelRelay node** plays a key role. Unlike standard Edge Nodes, in addition to running the traditional Agent (managing OpenResty for HTTPS/WAF processing), it co-locates the **Relay (frps tunnel manager)** service, responsible for listening to intranet client (OpenFlared) tunnel connections and relaying traffic.
In OpenFlare's intranet penetration system, the **TunnelRelay node** plays a key role. Unlike regular edge nodes, besides running the traditional Agent (hosting OpenResty for HTTPS/WAF processing), it also runs the **Relay (frps tunnel manager)** service on the same machine, listening for tunnel connections from intranet clients (OpenFlared) and relaying traffic.
---
## Prerequisites
Before deploying a TunnelRelay node, ensure:
Before deploying a TunnelRelay node, make sure:
1. **Registered as a TunnelRelay node**: Add a node of type `tunnel_relay` in the OpenFlare management console under "Node Management", and retrieve its node-specific `agent_token` or use the global `discovery_token`.
2. **Network Ports**:
- Ensure `bindPort` (the port frpc clients connect to, default `7000`) is accessible from the public/intranet client networks.
- Ensure `vhostHTTPPort` (the HTTP Vhost port, default `8080`) is free and not bound by other processes, as the Agent routes traffic to frps on this port.
3. **Software Dependencies** (Host deployment only):
- You must have an executable `frps` binary locally (recommended version `v0.61.0+` or the latest stable `v0.69.0`), or specify its path explicitly in the configuration.
1. **Registered as a TunnelRelay-type node**: in OpenFlare admin「Node Management」, add a node of type `tunnel_relay` and get its dedicated `agent_token`, or use the global `discovery_token`.
2. **Network ports**:
- `bindPort` (frpc connection port, default `7000`) must be reachable by public/intranet clients.
- `vhostHTTPPort` (HTTP Vhost port, default `8080`) must be free; the Agent exchanges traffic with frps on this port.
3. **Software dependency** (host deployment only):
- an executable `frps` binary locally, or an explicitly specified path via parameter.
---
## Configuration & Environment Variables
## Config File and Env Vars
`openflare-relay` reads `relay.json` in the working directory by default on startup. Overriding options via environment variables is fully supported.
`openflare-relay` reads `relay.json` in the current directory by default at startup, fully overridable via env vars.
### Configuration Fields Details
### Config Field Details
| JSON Field | Environment Variable | Description | Default Value |
| JSON field | Env var | Description | Default |
| --- | --- | --- | --- |
| `server_url` | `OPENFLARE_SERVER_URL` | OpenFlare Server API base URL | **None (Required)** |
| `agent_token` | `OPENFLARE_AGENT_TOKEN` | Node-specific Token | Mutually exclusive with below |
| `discovery_token` | `OPENFLARE_DISCOVERY_TOKEN` | One-time auto-registration Token | Mutually exclusive with above |
| `node_name` | `OPENFLARE_NODE_NAME` | Custom name for the node | Hostname by default |
| `node_ip` | `OPENFLARE_NODE_IP` | Outbound/listening IP of the node | Automatically detects real outbound IP |
| `frps_path` | `OPENFLARE_FRPS_PATH` | Path to the `frps` executable binary | `"frps"` |
| `data_dir` | `OPENFLARE_DATA_DIR` | Directory to store local data and generated `frps.toml` | `"./data"` |
| `state_path` | - | Path to store local state JSON file | `"{data_dir}/relay-state.json"` |
| `heartbeat_interval`| - | Heartbeat interval (integer ms or Go Duration string) | `10000` (10s) |
| `request_timeout` | - | HTTP request timeout duration | `10000` (10s) |
| `server_url` | `OPENFLARE_SERVER_URL` | OpenFlare Server API service address | **none (required)** |
| `agent_token` | `OPENFLARE_AGENT_TOKEN` | node-specific Token | one of these two |
| `discovery_token` | `OPENFLARE_DISCOVERY_TOKEN` | auto-registration Token | one of these two |
| `node_name` | `OPENFLARE_NODE_NAME` | node identifier name | local hostname by default |
| `node_ip` | `OPENFLARE_NODE_IP` | node egress/listen IP | auto-detected real egress IP |
| `frps_path` | `OPENFLARE_FRPS_PATH` | frps executable binary path | `"frps"` |
| `data_dir` | `OPENFLARE_DATA_DIR` | local data and generated `frps.toml` directory | `"./data"` |
| `state_path` | - | local state JSON record file path | `"{data_dir}/relay-state.json"` |
| `heartbeat_interval`| - | heartbeat period (ms int or Go Duration string) | `10000` (10s) |
| `request_timeout` | - | API request timeout | `10000` (10s) |
---
## Docker Deployment (Recommended)
## Running with Docker
Docker is the most convenient way to deploy a TunnelRelay node. The official Docker image embeds the `openflare-relay` controller and `frps v0.69.0` out of the box.
Docker is the most convenient deployment for a TunnelRelay node. The official image bundles the `openflare-relay` controller and the `frps` runtime — out of the box.
```bash
docker pull ghcr.io/rain-kl/openflare-relay:latest
@@ -50,53 +50,24 @@ docker rm -f openflare-relay 2>/dev/null || true
docker run -d --name openflare-relay --restart unless-stopped \
-p 7000:7000 \
-p 17500:17500 \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
-v openflare-relay-data:/var/lib/openflare-relay \
-v openflare-relay-data:/app/data \
ghcr.io/rain-kl/openflare-relay:latest
```
> [!TIP]
> The `-p 7000:7000` option maps the port `frpc` clients connect to. If a custom `relay_bind_port` is configured in the management console, change this port mapping on the host accordingly.
> The `-p 7000:7000` mapping is the port frpc clients connect to for relaying. If the admin panel configures a custom `relay_bind_port`, adjust the host port mapping accordingly.
> [!NOTE]
> **Enable the embedded frps Web UI**:
> If the Server control panel enables the relay traffic monitoring panel (i.e. `relay_frps_web_ui_enabled` set to `true` in DB/system settings), you need to map the Web port (default `17500`, controlled by `relay_frps_web_ui_port` in system settings) to the host via `-p 17500:17500`.
> The Web UI username is fixed to `admin`, and the password is the relay node's `agent_token`.
---
## Manual Host Deployment
If you prefer to run the Relay directly on a physical host or VM:
### 1. Compile the Binary
```bash
cd openflare-relay
go build -o openflare-relay ./cmd/relay
```
### 2. Prepare `relay.json`
Create a `relay.json` configuration file in the same directory as the executable:
```json
{
"server_url": "http://127.0.0.1:3000",
"agent_token": "your-relay-node-agent-token",
"frps_path": "/usr/local/bin/frps",
"data_dir": "./data",
"heartbeat_interval": "10s",
"request_timeout": "10s"
}
```
### 3. Start the Service
```bash
export LOG_LEVEL='info'
./openflare-relay -config ./relay.json
```
---
## Start & Validate
## Startup and Verification
### 1. View Process Logs
@@ -105,22 +76,17 @@ export LOG_LEVEL='info'
docker logs -f openflare-relay
```
If managed via systemd on Linux, execute:
```bash
journalctl -u openflare-relay -f
```
### 2. Verify Runtime State
### 2. Verify Runtime Status
After starting successfully, the Relay will:
- Send HTTP heartbeats to the control plane to register/go online.
- Fetch the latest frps base config from the control plane (including `bindPort`, `vhostHTTPPort`, and the auto-generated tunnel auth credential `auth_token`).
- Render the local `data/frps.toml` config file.
- Spawn the child process `frps -c data/frps.toml`.
- If the process exits unexpectedly, the Relay auto-restarts frps with exponential backoff (initial 1s, cap 60s).
Upon starting successfully, the Relay operates as follows:
- Sends HTTP heartbeats to register and go online with the control plane.
- Retrieves the active frps baseline settings (including `bindPort`, `vhostHTTPPort`, and the auto-generated `auth_token`).
- Automatically renders the `data/frps.toml` configuration locally.
- Spawns the subprocess `frps -c data/frps.toml`.
- If the `frps` process crashes, the Relay automatically restarts it after 2 seconds.
### 3. Confirm in the Admin Panel
### 3. Verify in the Management Console
Log into the management console and navigate to **"Node Management"** to verify:
- The TunnelRelay node status is marked as **"Online"**.
- The Node Type is correctly displayed as **Relay Node** and the frps status displays as **Healthy**.
Log in to the admin panel, navigate to **「Node Management」**, and confirm:
- The TunnelRelay node status is marked **「Online」**.
- The node type is correctly marked as **Relay node** and the frps runtime state is **Healthy**.
+250 -120
View File
@@ -1,170 +1,300 @@
# Launch Server
# Start the Server
You will learn: How to build the admin frontend from source, start the OpenFlare Server, choose between SQLite or PostgreSQL, and access Swagger.
You will learn: how to deploy with Docker (quick start, production-recommended, advanced) and how to deploy the OpenFlare Server locally from source.
OpenFlare Server is a Gin + GORM monolithic control plane, responsible for managing the Admin UI, Admin API, Agent API, configuration rendering, version publishing, data storage, and aggregated queries.
The OpenFlare Server is a Gin + GORM monolithic control plane responsible for the admin UI, admin API, Agent API, config rendering, version release, data storage, and aggregation queries.
## Prerequisites
> [!IMPORTANT]
> **About external dependencies**:
> OpenFlare has built-in support for background async tasks (Asynq framework). Therefore, **regardless of deployment mode, Redis (or Valkey) is required**. The main difference between deployment options is the primary relational DB choice (SQLite vs PostgreSQL) and whether tracing (Jaeger) is enabled.
> For high business traffic, ClickHouse is recommended for log storage.
| Item | Requirement |
| --- | --- |
| Go | `1.25+` |
| Node.js | `18+` |
| pnpm | Recommended enabling via `corepack enable` |
| Database | SQLite parent directory must be writable, or a reachable PostgreSQL instance |
> [!TIP]
> **ClickHouse server performance config (recommended mount)**
> The control plane is typically a small host (e.g. 3c6g). The `performance.xml` provided in the repo tightens the background merge/mutation thread pools, avoiding high idle CPU or ClickHouse 25.x startup validation failures on small machines.
> Mount the local `./config/clickhouse/performance.xml` as a single file at `/etc/clickhouse-server/config.d/performance.xml` to keep the official image's built-in Docker network listening config.
In production environments, we highly recommend explicitly configuring `SESSION_SECRET` and prioritizing PostgreSQL.
## Build the Admin Frontend
The Go Server hosts static assets located in `openflare-server/web/build`. Before starting the Server from source, build the frontend:
Pull the config locally before deploying:
```bash
cd openflare-server/web
corepack enable
pnpm install
pnpm build
mkdir -p ./config/clickhouse
curl -fsSL -o ./config/clickhouse/performance.xml \
https://raw.githubusercontent.com/Rain-kl/OpenFlare/refs/heads/main/config/clickhouse/performance.xml
```
Common frontend quality checks:
Add it to the ClickHouse service `volumes` (alongside the data volume):
```bash
pnpm lint
pnpm typecheck
pnpm test
```yaml
volumes:
- ./data/clickhouse_data:/var/lib/clickhouse # or named volume
- ./config/clickhouse/performance.xml:/etc/clickhouse-server/config.d/performance.xml:ro
```
## Start with SQLite
```bash
cd openflare-server
export SESSION_SECRET='replace-with-a-long-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
go run .
```
By default, the Server listens on port `3000`. Access it at:
```text
http://localhost:3000
```
## Start with PostgreSQL
```bash
cd openflare-server
export SESSION_SECRET='replace-with-a-long-random-string'
export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
export LOG_LEVEL='info'
go run .
```
If `DSN` is set, it takes precedence over SQLite. When both `DSN` and the legacy `SQL_DSN` exist, `DSN` is prioritized.
If the target PostgreSQL database is empty and a local SQLite database exists at `SQLITE_PATH`, the Server automatically migrates the SQLite data into PostgreSQL during startup, outputting the migration progress in the logs.
## Start with Docker
Deploying with Docker avoids the hassle of setting up local Go and Node.js environments. OpenFlare provides official Dockerfiles and Compose configurations to support independent container startups and multi-service orchestrations.
### 1. Quick Start via Docker Run (SQLite Example)
Ensure that a local directory for persisting databases and logs has been created. Run the following command to start the Server:
```bash
# Create local mount directory
mkdir -p ./openflare-data
# Start the container
docker run -d \
--name openflare-server \
-p 3000:3000 \
-v $(pwd)/openflare-data:/data \
-e SESSION_SECRET='replace-with-a-long-random-string' \
-e SQLITE_PATH='/data/openflare.db' \
-e GIN_MODE='release' \
-e LOG_LEVEL='info' \
ghcr.io/rain-kl/openflare:latest
```
Startup parameters:
* **`-p 3000:3000`**: Maps port `3000` on the host to port `3000` inside the container.
* **`-v $(pwd)/openflare-data:/data`**: Mounts the local directory to `/data` in the container, ensuring that the SQLite database `openflare.db` is not lost when restarting or rebuilding the container.
* **`SESSION_SECRET`**: The session signing hash key (required).
After modifying `performance.xml`, run `docker compose restart clickhouse` for it to take effect.
---
### 2. One-click Startup via Docker Compose (Integrated PostgreSQL)
## Method 1: Docker Deployment (recommended)
We recommend using Docker Compose in production environments to orchestrate an independent PostgreSQL database and establish high-availability relationships.
Docker deployment avoids configuring Go and Node.js frontend build environments locally. Choose one of the three options based on your hardware and needs:
Create a `docker-compose.yml` file:
### 1. Quick Start (SQLite + Redis)
> **Use case**: testing/experience, lightweight single-machine deployment.
>
> **Features**: primary relational DB is SQLite.
Create a `docker-compose.yaml`:
```yaml
version: '3.8'
services:
openflare:
image: ghcr.io/rain-kl/openflare:latest
container_name: openflare-server
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./openflare-data:/data
- ./uploads:/app/uploads
environment:
TZ: Asia/Shanghai
APP_SESSION_SECRET: 'replace-with-a-long-random-string' # replace with a long random string in production
DB_ENABLED: "false" # disables PostgreSQL, auto-enables the built-in SQLite fallback
SQLITE_PATH: "/data/openflare.db"
REDIS_ENABLED: "true"
REDIS_ADDR: "redis:6379"
depends_on:
redis:
condition: service_healthy
redis:
image: valkey/valkey:8.0-alpine
restart: unless-stopped
command: ["valkey-server", "--appendonly", "yes"]
volumes:
- ./data/valkey:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
```
---
### 2. Small-Traffic Business (PostgreSQL + Redis)
> **Use case**: production, small-to-medium traffic; PostgreSQL won't be the log-write bottleneck.
Create a `docker-compose.yaml`:
```yaml
services:
openflare:
image: ghcr.io/rain-kl/openflare:latest
restart: unless-stopped
env_file: .env
environment:
TZ: ${TZ:-Asia/Shanghai}
ports:
- "3000:3000"
volumes:
- openflare_uploads:/app/uploads
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
POSTGRES_DB: ${DB_NAME:-openflare}
POSTGRES_USER: ${DB_USERNAME:-openflare}
POSTGRES_PASSWORD: ${DB_PASSWORD:-replace-with-strong-password}
volumes:
- ./postgres-data:/var/lib/postgresql/data
- openflare_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-openflare} -d ${DB_NAME:-openflare}"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
redis:
image: valkey/valkey:8.0-alpine
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-random-string
SQLITE_PATH: /data/openflare.db
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
command: ["valkey-server", "--appendonly", "yes"]
volumes:
- ./openflare-data:/data
- openflare_redis_data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
volumes:
openflare_uploads:
openflare_postgres_data:
openflare_redis_data:
```
Start the services:
Create a matching `.env` file for system env vars (copy and modify the root `.env.example`):
```bash
curl -o .env.example https://raw.githubusercontent.com/Rain-kl/OpenFlare/refs/heads/main/.env.example
cp .env.example .env
# edit .env: fill in DB, Redis, passwords, and APP_SESSION_SECRET
docker compose up -d
```
Compose configuration options:
* **`depends_on` and `healthcheck`**: Uses PostgreSQL's health check (`pg_isready`) to ensure that the database is fully initialized and ready before launching the OpenFlare Server, preventing panics from failed database connection attempts on first launch.
* **Separated Data Volume Mounts**: PostgreSQL data is mounted under `./postgres-data`, and OpenFlare data and backups are mounted under `./openflare-data`, making backups and maintenance simple.
---
## CLI Arguments
### 3. Advanced (full orchestration with Jaeger tracing)
```bash
go run . --port 3000 --log-dir ./logs
> **Use case**: high traffic; needs trace performance metrics.
>
> **Features**: on top of the "production-recommended" bundle, stores logs with ClickHouse and uses Jaeger as the OpenTelemetry (OTel) tracing backend.
Create a `docker-compose.yaml`:
```yaml
version: '3.8'
services:
openflare:
image: ghcr.io/rain-kl/openflare:latest
restart: unless-stopped
env_file: .env
environment:
TZ: ${TZ:-Asia/Shanghai}
OTEL_EXPORTER_OTLP_ENDPOINT: "http://jaeger:4317"
OTEL_EXPORTER_OTLP_INSECURE: "true"
OTEL_SAMPLING_RATE: "1.0" # sampling rate; 1.0 samples all traces
ports:
- "3000:3000"
volumes:
- openflare_uploads:/app/uploads
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
jaeger:
condition: service_started
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME:-openflare}
POSTGRES_USER: ${DB_USERNAME:-openflare}
POSTGRES_PASSWORD: ${DB_PASSWORD:-replace-with-strong-password}
volumes:
- openflare_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-openflare} -d ${DB_NAME:-openflare}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: valkey/valkey:8.0-alpine
restart: unless-stopped
command: ["valkey-server", "--appendonly", "yes"]
volumes:
- openflare_redis_data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
jaeger:
image: jaegertracing/jaeger:2.19.0
restart: unless-stopped
environment:
TZ: ${TZ:-Asia/Shanghai}
ports:
- "16686:16686" # Web UI port
- "4317:4317" # OTLP gRPC receive port
- "4318:4318" # OTLP HTTP receive port
clickhouse:
image: clickhouse/clickhouse-server:25.3-alpine
restart: unless-stopped
environment:
CLICKHOUSE_DB: ${CLICKHOUSE_NAME:-openflare}
CLICKHOUSE_USER: ${CLICKHOUSE_USERNAME:-default}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-replace-with-clickhouse-password}
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
TZ: ${TZ:-Asia/Shanghai}
ulimits:
nofile:
soft: 262144
hard: 262144
volumes:
- openflare_clickhouse_data:/var/lib/clickhouse
- ./config/clickhouse/performance.xml:/etc/clickhouse-server/config.d/performance.xml:ro
healthcheck:
test: ["CMD", "clickhouse-client", "--user", "${CLICKHOUSE_USERNAME:-default}", "--password", "${CLICKHOUSE_PASSWORD:-replace-with-clickhouse-password}", "--query", "SELECT 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
volumes:
openflare_uploads:
openflare_postgres_data:
openflare_redis_data:
openflare_clickhouse_data:
```
| Argument | Description | Default Value |
| --- | --- | --- |
| `--port` | The port the Server listens to | `3000` |
| `--log-dir` | The directory to write logs to | Empty, outputs to stdout |
| `--version` | Outputs version and exits | `false` |
| `--help` | Outputs help and exits | `false` |
Start and verify:
```bash
mkdir -p ./config/clickhouse
curl -fsSL -o ./config/clickhouse/performance.xml \
https://raw.githubusercontent.com/Rain-kl/OpenFlare/refs/heads/main/config/clickhouse/performance.xml
curl -o .env.example https://raw.githubusercontent.com/Rain-kl/OpenFlare/refs/heads/main/.env.example
cp .env.example .env
# edit .env and make sure APP_SESSION_SECRET password is set
docker compose up -d
```
After startup, open `http://localhost:16686` to view the Jaeger monitoring UI and system span traces.
---
## First Login
Default credentials:
The Server listens on port `3000` by default; open `http://localhost:3000` in a browser after startup.
Default admin account:
| Username | Password |
| --- | --- |
| `root` | `123456` |
| `admin` | `12345678` |
Please change the default password immediately after your first login.
> [!WARNING]
> For your system's security, change the default password immediately in your profile settings after the first login.
---
## Distributed Deployment
In large production deployments, split the Server into multiple processes by responsibility:
```bash
go run main.go api # API service for admin panel and node communication only
go run main.go worker # background task Worker service only
go run main.go scheduler # scheduled task Scheduler service only
```
+7 -39
View File
@@ -1,52 +1,20 @@
# Upgrade & Maintenance
You will learn: How to upgrade the Server and the Agent, how to clean up observability data, and which validation commands to execute before and after maintenance.
You will learn: how to upgrade the Server and Agent, how to clean up observability data, and which verification commands to run before and after maintenance.
Before upgrading, verify the currently active version, the most recent Agent application results, and your database backup strategy. In production environments, never trigger upgrades while a configuration is being published, during large-scale Agent reconnections, or while database migrations are in progress.
Before upgrading, confirm the current active version, the most recent Agent apply result, and the DB backup policy. In production, don't upgrade while a config release, a large-scale Agent reconnect, or a DB migration is in progress.
## Server Upgrade
Root users can check and trigger stable Server upgrades in the top header of the management console. You can also trigger upgrades by uploading the compiled Server binary in the console.
To deploy preview releases, manually check the GitHub Releases page. We highly recommend prioritizing stable releases in production environments.
Verify after upgrading:
Pull the latest image and upgrade:
```bash
docker compose ps
docker compose logs -n 100 openflare
docker compose pull
docker compose up
```
If deployed from source, restart the Server and verify that no database migration or startup errors appear in the logs.
For source deployments, restart the Server and confirm the logs show no DB migration or startup errors.
## Agent Upgrade
Node Agents automatically update following stable releases by default. Upgrading to preview releases requires a manual trigger.
You can re-execute the installation script to redeploy or force-update the Agent:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
--server-url http://your-server:3000 \
--agent-token YOUR_AGENT_TOKEN
```
Note: Re-executing the current installation script wipes the entire installation directory, including the existing `agent.json`, local states, cached databases, and downloaded binaries. Ensure you have the node Token handy before executing the script.
Verify after upgrading:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -n 100 --no-pager
```
## Data Maintenance
The management console's Settings page maintains options for automatic cleanup of observability data:
| Parameter | Description |
| --- | --- |
| `DatabaseAutoCleanupEnabled` | Toggles daily automatic cleanup |
| `DatabaseAutoCleanupRetentionDays` | Data retention duration in days, minimum 1 day |
When enabled, the Server cleans up access logs, metrics snapshots, and request reports at 3:00 AM daily.
The Agent only caches runtime config and state files locally — no business data. To upgrade, directly pull the latest image and recreate the container. For specific deployment commands and install methods, see **[Access Agent](./agent.md)**.
+111 -115
View File
@@ -1,55 +1,53 @@
# Agent Design Document
# Agent Design
You will learn: Agent design principles, core functional modules, interaction links with the Server, and how configuration applications are secured and made reliable through immutable version models and the three-stage disaster recovery rollback mechanism.
You will learn: the Agent's design principles, core functional modules, interaction chain with the Server, and how the immutable version model and three-stage disaster recovery guarantee config-apply safety and reliability.
---
## Requirements Analysis
In distributed reverse proxy and edge security gateway scenarios, the Agent plays a central role in connecting the control plane (Server) and the data plane (OpenResty). Since the Agent runs on the user's actual node server, its design must adhere to the following core security and high-availability requirements:
In distributed reverse-proxy and edge-security gateway scenarios, the Agent is the core bridge between the control plane (Server) and the data plane (OpenResty). Since the Agent runs on the user's actual node server, its design must satisfy these core security and HA requirements:
1. **Active Pull (Pull Model) instead of Push**: The Server does not hold the SSH keys of the nodes, nor does it actively initiate inbound connections to the nodes. All control directives and configuration updates are actively pulled by the Agent via heartbeats or long-lived connections (WebSockets). This eliminates inbound firewall security risks on the node side and prevents control channels from being hijacked.
2. **Minimal Intrusiveness**: The Agent runs as an independent Go binary process. It only interacts with the local OpenResty process through file-based configuration rewriting and signal notifications, without interfering with other system services on the node.
3. **Robust Disaster Recovery & Self-Healing**: Since network jitter, disk exhaustion, or erroneous configurations can easily lead to configuration sync failures, the Agent must possess zero-dependency local rollback and self-healing capabilities, strictly preventing a single configuration error from causing a complete node outage.
4. **Pure Data and State Landing**: The Agent is only responsible for executing file generation and control intentions rendered by the Server. It does not carry complex control plane duties like business logic validation or multi-tenant authorization, ensuring the node side remains highly efficient and lightweight.
1. **Active pull (Pull model), not passive receive**: the Server doesn't hold node SSH keys and never initiates inbound connections to nodes. All control instructions and config updates are pulled upward by the Agent via heartbeat or WebSocket. This removes inbound-firewall security risks on nodes and prevents control-channel hijacking.
2. **Minimal invasiveness**: the Agent runs as a standalone Go binary, interacting with the local OpenResty process only via file-based config rewriting and signal notifications — no interference with other system services on the node.
3. **Strong disaster recovery and self-healing**: since network jitter, full disks, or bad configs can easily break config sync, the Agent must have zero-dependency local rollback self-healing to prevent one bad config from taking down the whole machine.
4. **Pure data and state landing**: the Agent only carries Server-rendered files and control intent to landing; it contains no complex business validation or multi-tenant auth — control-plane duties stay on the Server, keeping the node efficient and light.
---
## Core Capabilities
## Core Features
The Agent is composed of the following core sub-modules, cooperating to manage its complete lifecycle:
The Agent mainly consists of these submodules cooperating for its full lifecycle:
| Module Name | Directory | Responsibilities |
| Module | Directory | Responsibility |
| :--- | :--- | :--- |
| **Config Sync** | `sync/` | Pulls full configuration packages, writes files, triggers reloads, and records and reports sync statuses. |
| **Heartbeat** | `heartbeat/` | Periodically reports node health and resource metrics to the Server and retrieves the latest active version summary. |
| **WebSocket** | `wsclient/` | Maintains a persistent connection with the Server, providing sub-second real-time configuration pushes and commands. |
| **OpenResty Control** | `nginx/` | Executes Nginx config validation (`openresty -t`), rewrites, graceful reloads (`reload`), and process auto-start. |
| **Local State Store** | `state/` | Persistently records local applied versions, error logs, and buffers unsent observability metrics. |
| **Self-Updater** | `updater/` | Listens to Server self-update commands, securely pulls new binary versions, and completes in-place upgrades. |
| **Observability** | `observability/` | Collects host CPU/memory/disk and Nginx performance metrics, processes access logs, and uploads them. |
| **GeoIP Maintenance** | `geoipdata/` `geoipupdate/` | Maintains and updates the local GeoIP database periodically to support WAF country-level filtering. |
| **Config sync** | `sync/` | pull full config packages, write files, trigger reloads, record and report sync state. |
| **Heartbeat** | `heartbeat/` | periodically report node health, resource metrics, and fetch the latest active version summary. |
| **WebSocket** | `wsclient/` | keep a long connection to the Server for second-level real-time config push and control-plane instructions. |
| **OpenResty control** | `nginx/` | run Nginx config validation (`openresty -t`), rewriting, smooth reload, and process auto-start. |
| **Local state** | `state/` | persist local applied version, error logs, and buffered observability metrics not yet reported. |
| **Self-update** | `updater/` | listen for Server self-update instructions, safely fetch new binaries, and hot-upgrade in place. |
| **Observability** | `observability/` | collect host resource readings, OpenResty health/connections, and tail access-log details for reporting; **no** business pre-aggregation like UV/TopN/throughput. See [Edge Observability & Business Traffic Stats](./observability-design.md). |
| **GeoIP maintenance** | `geoipdata/` `geoipupdate/` | maintain and periodically update the local GeoIP DB for WAF geo filtering. |
---
## Interaction Flows with Server
## Interaction Chain with the Server
The Agent communicates with the control plane through **Token-based Auto-Registration** and a **Dual-channel Heartbeat/WebSocket** system during its lifecycle.
The Agent communicates with the control plane via **Token-based auto-registration** and **heartbeat/WebSocket dual channels** over its lifecycle.
### 1. Auto-Registration Flow
If `access_token` in the local `agent.json` is empty at startup but `discovery_token` is configured, auto-registration triggers:
1. The Agent sends a registration request to `/api/v1/agent/nodes/register` with local hardware summary, IP, and hostname.
2. The Server validates the `discovery_token`, generates a unique `NodeID` and dedicated `AccessToken` (i.e. `agent_token`), and returns them.
3. The Agent writes the dedicated Token into the local config file, erases the one-time `discovery_token`, and all future communication authenticates with the dedicated `AccessToken`.
If the Agent starts with an empty `access_token` in its local `agent.json`, but has a `discovery_token` configured, it triggers the auto-registration flow:
1. The Agent sends a registration request to `/api/agent/register`, carrying a local hardware fingerprint, IP, and hostname.
2. After validating the `discovery_token`, the Server generates a unique `NodeID` and a dedicated `AccessToken` (i.e., `agent_token`) in the database and returns them.
3. The Agent writes the dedicated Token to its local configuration file, clears the one-time `discovery_token`, and uses the `AccessToken` for all subsequent authenticated communications.
### 2. Dual-Channel Heartbeat & Sync Mechanism
* **HTTP Polling (Fallback and Detection)**: The Agent sends POST heartbeat packets at configured `heartbeat_interval` intervals by default. It reports health metrics while retrieving the currently active configuration version summary (Version & Checksum).
* **WebSocket Channel (Real-time Communication)**: Upon a successful HTTP heartbeat, the Agent automatically attempts to upgrade the connection to WebSocket (`/api/agent/ws`).
* Once the WS connection is established, heartbeats and metrics reporting shift entirely to the WS pipeline, reducing network overhead.
* When the Server publishes or activates a new version, it broadcasts a notification to the Agent via WS. The Agent triggers the synchronization flow **immediately** upon receiving the change event, achieving sub-second configuration deployment.
* If the WS connection drops due to network issues, the Agent automatically falls back to HTTP polling and uses an exponential backoff algorithm to attempt rebuilding the WS channel.
### 2. Dual-Channel Heartbeat and Sync
* **HTTP polling channel (fallback & probe)**: the Agent POSTs heartbeats at the configured `heartbeat_interval` by default, reporting metrics while fetching the current active version summary (Version & Checksum).
* **WebSocket channel (real-time)**: after a successful HTTP heartbeat, the Agent auto-upgrades to WebSocket (`/api/v1/agent/ws`).
* Once established, heartbeat and metric reporting fully move to the WS pipe, reducing network overhead.
* When the Server releases/activates a new version, it broadcasts to Agents via WS. The Agent triggers sync **immediately** on the change event for second-level config effect.
* If the WS link drops due to network issues, the Agent degrades to HTTP polling and retries WS with exponential backoff.
### 3. Interaction Sequence Diagram
@@ -60,122 +58,120 @@ sequenceDiagram
participant OR as Local OpenResty
participant Server as OpenFlare Server
Note over Agent: First Startup (No AccessToken)
Agent->>Server: 1. Auto-registration request (carrying discovery_token)
Server-->>Agent: 2. Issue NodeID & dedicated AccessToken (agent_token)
Note over Agent: Store Token in local configuration file
Note over Agent: first startup (no AccessToken)
Agent->>Server: 1. auto-registration request (with discovery_token)
Server-->>Agent: 2. issue NodeID and dedicated AccessToken (agent_token)
Note over Agent: store Token in local config file
rect rgb(240, 248, 255)
Note over Agent, Server: HTTP Fallback & WebSocket Upgrade
Agent->>Server: 3. Send HTTP Heartbeat (report system metrics & health)
Server-->>Agent: 4. Return ActiveConfig summary & AgentSettings
Agent->>Server: 5. Initiate WebSocket upgrade request (/api/agent/ws)
Server-->>Agent: 6. Upgrade successful (persistent bi-directional channel)
Note over Agent, Server: HTTP fallback and WebSocket upgrade
Agent->>Server: 3. send HTTP Heartbeat (report system state and health)
Server-->>Agent: 4. return ActiveConfig summary and AgentSettings
Agent->>Server: 5. request WebSocket upgrade (/api/v1/agent/ws)
Server-->>Agent: 6. upgrade success (bidirectional persistent real-time channel)
end
rect rgb(245, 245, 245)
Note over Agent, Server: Real-time Configuration Publication
Note over Server: Administrator clicks publish config in UI
Server->>Agent: 7. Broadcast active config summary via WS (WSMessageTypeActiveConfig)
Agent->>Server: 8. Request full configuration details (carrying target Version/Checksum)
Server-->>Agent: 9. Return complete configuration snapshot (Nginx configs, certs, WAF rules, etc.)
Note over Agent: Backup old files, write new config to local temp path
Agent->>OR: 10. Execute config syntax validation (openresty -t)
OR-->>Agent: 11. Return validation result (OK)
Agent->>OR: 12. Send graceful reload signal (openresty -s reload)
Agent->>Server: 13. Report application success status (Apply Log & ActiveVersion)
Note over Agent, Server: real-time config release/apply chain
Note over Server: admin clicks publish config in the UI
Server->>Agent: 7. broadcast new config summary via WS (WSMessageTypeActiveConfig)
Agent->>Server: 8. request full config details (with target Version/Checksum)
Server-->>Agent: 9. return full config snapshot (Nginx config, certs, WAF rules, etc.)
Note over Agent: back up old files, write new config to local temp path
Agent->>OR: 10. run config syntax validation (openresty -t)
OR-->>Agent: 11. return validation result (OK)
Agent->>OR: 12. smooth reload signal (openresty -s reload)
Agent->>Server: 13. report apply success (Apply Log & ActiveVersion)
end
```
---
## Control of OpenResty
## OpenResty Control
The Agent implements end-to-end closed-loop control of the data plane OpenResty, including configuration rendering, syntax validation, graceful reloading, and exception state capturing:
The Agent's control over the data-plane OpenResty forms an end-to-end loop: config landing, syntax validation, smooth reload, and abnormal-state capture.
### 1. Configuration Layout on Disk
### 1. Config File Landing Organization
After a successful sync, the Agent writes config under `data_dir` (default relative `etc/nginx/`, `etc/openflare/`, `var/lib/openflare/`; exact paths follow `main_config_path`, `route_config_path`, `cert_dir`, `lua_dir`, `runtime_config_dir`, `pages_dir` in `agent.json`):
* `nginx.conf`: the main config (replaces relevant placeholders, configures performance params, Shared Dictionaries, and the global Server).
* `conf.d/openflare_routes.conf`: the route config (generated by the Agent; contains all proxied sites' Server blocks, cert paths, cache, and rate-limit directives).
* `certs/`: certificate dir (files named `{cert_id}.crt` and `{cert_id}.key`).
* `lua/waf/` and `lua/pow/`: dedicated Lua runtime scripts for WAF and anti-CC challenges.
* `etc/openflare/waf_config.json` and `waf_ip_groups.json`: structured rule configs for the WAF filtering engine.
* `pages_dir`: the Pages static site deployment dir, default `data_dir/var/lib/openflare/pages`. When the active config references a Pages **project**, the Agent requests the control plane's「latest active package」(hash + package) by `project_id`, streams to a temp file with real response-size limits and SHA-256 validation, then safely extracts to `projects/{project_id}/releases/{hash}`. After extraction it rechecks file count and total bytes; absolute hard caps are 2 GiB package, 1,000 files, 8 GiB single-file/total. It then atomically switches `current` and **immediately deletes other historical releases of the same project** (only latest kept). Switching the active deployment within a project doesn't require republishing the main config; multi-project reconciliation isolates single-project failures.
Upon successful sync, the Agent writes configuration files to `/etc/nginx/openflare-lua/` (or the configured `LuaDir`) according to a strict physical structure:
* `nginx.conf`: Main configuration file (replaces absolute path placeholders, configures performance parameters, shared dictionaries, and global server blocks).
* `routes.conf`: Route configuration file (generated by the Agent, containing all website server blocks, certificate paths, cache settings, and rate limit directives).
* `certs/`: Certificate storage directory (files named as `{cert_id}.crt` and `{cert_id}.key`).
* `waf/` and `pow/`: Dedicated Lua runtime scripts required for WAF and CC mitigation.
* `waf_config.json` and `waf_ip_groups.json`: Structured rules and IP databases required by the WAF filtering engine.
### 2. Refined Reload Operations
1. **Backup Current Config**: Before writing new files, the Agent copies the existing configuration files to a `.backup` directory, keeping a complete rollback snapshot.
2. **Write and Replace Placeholders**: Writes the pulled templates, automatically replacing absolute path placeholders (e.g., `__OPENFLARE_LUA_DIR__`) with actual local execution paths.
3. **Syntax Validation**: Calls `openresty -t -c <temp_nginx.conf>` to run a strict syntax test.
4. **Graceful Reload**: If validation passes, the Agent moves the files to the official paths and executes `openresty -s reload`. If OpenResty is not running, it launches the process.
5. **Exception Capture**: If validation or reload fails, the Agent intercepts the standard error output (stderr) and extracts the first 2000 characters of the detailed error log.
### 2. Fine-Grained Reload Actions
1. **Back up current config**: before writing new files, copy existing config to a `.backup` temp dir, keeping a full scene snapshot.
2. **Write and replace placeholders**: write the latest template, replacing absolute-path placeholders (e.g. `__OPENFLARE_LUA_DIR__`, `__OPENFLARE_PAGES_DIR__`) with local actual runtime paths.
3. **Syntax validation**: run `openresty -t -c <temp_nginx.conf>` for strict syntax testing.
4. **Smooth reload**: on validation pass, move the new config to the formal path and run `openresty -s reload`. If OpenResty isn't started, start the process with the current config.
5. **Capture exceptions**: on validation/reload failure, the Agent captures command stdout/stderr as failure details for reporting.
---
## Publishing & Config Application Model
## Release and Config Apply Model
OpenFlare discards the fragile mechanism of dynamically patching node configurations, instead using an **immutable configuration version publishing model**.
OpenFlare uses an **immutable config version release model**, not online dynamic patching of node configs.
```text
Edit rules -> Preview / View diff -> Publish -> Generate full configuration version -> Activate version -> Agent pulls -> Local application -> Report result
modify rules -> preview / view diff -> release -> generate full config version -> activate version -> Agent pulls -> local apply -> report result
```
### 1. Core Design Principles
* **Full release**: each release compiles all enabled routes, certs, Pages deployment references, and global/local WAF rules on the control plane in one pass, generating a full version with a unique `checksum`.
* **Version format**: `YYYYMMDD-NNN` incrementing format for intuitive, monotonically increasing version history.
* **Globally single active version**: only one globally active config version exists at a time. Rollback doesn't reverse-patch; just set a historical healthy version to `active`, and Agents re-pull and apply it.
* **Complete Publication**: Every publication compiles all enabled proxy routes, certificates, and global/custom WAF rules at once, generating a complete version package with a unique `checksum`.
* **Version Format**: Uses the `YYYYMMDD-NNN` incremental format, ensuring version histories are intuitive and strictly monotonic.
* **Global Single Active Version**: The system supports only one globally `active` configuration version at any given time. Rollbacks do not require reverse patching; they simply transition an older healthy version to the `active` state, and the Agent pulls and applies it.
### 2. Three-Stage Disaster Recovery & Rollback Mechanism
If the Agent fails to apply a configuration (or reload fails), it automatically triggers the following three-stage self-healing pipeline:
### 2. Three-Stage Disaster Recovery Rollback
When the Agent detects a config apply (or smooth reload) failure, it auto-activates this three-stage anti-outage chain:
```mermaid
graph TD
A[Config Application Failed] --> B[Stage 1: Attempt Local Backup Recovery]
B -- Backup Exists --> C[Write Local Backup Files]
C --> D[Run openresty -t Validation]
D -- Validation OK --> E[Reload Old Configuration]
D -- Validation Failed --> F[Proceed to Stage 2]
B -- No Backup --> F[Stage 2: Write Built-in Safe Fallback Config]
F --> G[Write fallback nginx.conf: Listen on Port 80 Only]
G --> H[Enable stub_status health checks]
G --> I[Return 503 for all other routes & block errors]
G --> J[Attempt to launch OpenResty to maintain basic survival]
J --> K[Proceed to Stage 3]
E --> L[Report Apply Warning]
K --> M[Block Local Repeated Application of Failed Version]
M --> N[Report Apply Error with detailed logs]
A[config apply failed] --> B[stage 1: try local backup restore]
B -- backup file exists --> C[write local backup files]
C --> D[run openresty -t validation]
D -- validation ok --> E[reload to restore old version]
D -- validation failed --> F[enter stage 2]
B -- no backup --> F[stage 2: write built-in safe fallback config]
F --> G[write fallback nginx.conf: listen on 80 only]
G --> H[enable stub_status health check]
G --> I[other routes return 503 uniformly and block bad configs]
G --> J[try starting OpenResty to keep basic liveness]
J --> K[enter stage 3]
E --> L[report Apply Warning]
K --> M[locally block re-applying the bad version]
M --> N[report Apply Error with detailed error]
```
1. **Stage 1: Local Backup Rollback**
* The Agent attempts to restore the main configuration, routes, and certificates from the `.backup` directory.
* It runs `openresty -t` validation on the restored backup. If successful, it reloads and reports a `Warning` to the Server (Warning: failed to apply new version, automatically rolled back to the previous healthy version).
2. **Stage 2: Built-in Safe Fallback Runtime**
* If no local backup exists (e.g., first deployment failed) or if the rollback validation fails, the Agent activates the ultimate self-healing mechanism: writing a **built-in safe fallback configuration**.
* **Fallback Configuration Specification**:
* Listens only on port `80`, containing no real user reverse proxy routes.
* The `/openflare/stub_status` endpoint returns a healthy response, while all other requests uniformly return a `503 Service Unavailable` status code with the fixed response body `OpenFlare: No Valid Configuration`.
* It attempts to launch OpenResty with this minimal configuration. This keeps the Nginx process alive, preserving underlying health probes and metric endpoints, preventing containers/pods from being repeatedly killed and restarted by orchestration systems, while keeping sensitive routes secure.
3. **Stage 3: Local Configuration Blocking**
* The Agent records the failing configuration's `version + checksum` in its local state store blacklist.
* Until the control plane activates a new configuration (resulting in a changed `checksum`), the Agent's heartbeat blocks repeated synchronization pulls of this erroneous version, preventing nodes from entering an infinite loop of "heartbeat -> pull failing config -> crash rollback".
1. **Stage 1: local backup fallback**
* The Agent tries restoring the main config, routes, and certs from the previously saved `.backup` dir.
* After writing backup files, re-run `openresty -t`. On success, reload back and report `Warning` to the Server (new version apply failed; auto-rolled back to the last healthy version).
2. **Stage 2: built-in safe fallback runtime**
* If no local backup config exists (e.g. first deployment with a bad config), or the restored backup still fails validation, the Agent activates the final self-healing mechanism — writing the **built-in safe fallback config**.
* **Safe fallback spec**:
* listens only on port `80`, containing no user real reverse proxy routes.
* Everything except the `/openflare/stub_status` health-check route (which returns normally) returns `503 Service Unavailable` with a fixed body `OpenFlare: No Valid Configuration`.
* Tries starting OpenResty with this minimal config. This keeps the Nginx process itself alive, preserves the underlying health check/probe channel, prevents container/Pod restart loops from failed health checks, and protects sensitive routes.
3. **Stage 3: local config blocking**
* The Agent records the crash-causing config `version + checksum` in a blocklist in the local state store.
* Until the control plane activates a new config (`checksum` changes), the Agent heartbeat blocks re-pulling that bad version — preventing the "heartbeat → pull crash config → crash rollback" infinite loop.
### 3. WAF IP Group Asynchronous Runtime Synchronization
### 3. WAF IP Group Runtime Async Sync
To avoid high-frequency malicious-IP blocklist changes constantly triggering full main-config releases and reloads (smooth reload still has slight CPU and connection overhead on Nginx), IP group members use an **async differential sync** decoupled from release versions:
To prevent highly volatile IP blacklists from triggering frequent full config publications and Nginx reloads (which still incur minor CPU and connection overhead), WAF IP groups are synchronized via an **asynchronous differential sync design**:
* **Static Publication Snapshot**: The `waf_config.json` generated upon publication only contains the group ID reference mapping (i.e., `ip_whitelist_group_ids` / `ip_blacklist_group_ids`) and does not contain the actual list of IP addresses.
* **Heartbeat Differential Check**: The Agent uploads its locally cached IP groups MD5 checksum map in its heartbeat.
* **Differential Delivery**: The Server compares checksums and only delivers missing or modified IP groups, which are written directly to `waf_ip_groups.json` on the node without reload.
* **WebSocket Real-time Push**: When an administrator updates an IP group, or a threat intelligence subscription successfully pulls, or a security rule triggers a temporary block, the Server immediately broadcasts the IP group update package via WebSocket. The Agent receives and applies it instantly **without Nginx reloads**.
* **Static release snapshot**: the released `waf_config.json` only contains rule groups' references to IP groups (`ip_whitelist_group_ids` / `ip_blacklist_group_ids`), not the concrete IP member lists.
* **Heartbeat differential comparison**: the Agent reports the MD5 Checksum map of locally cached IP groups in heartbeat packets.
* **Differential dispatch**: the Server compares the hashes of IP groups referenced by the current active version and only dispatches missing or changed members, written to the local `waf_ip_groups.json` for fast differential sync.
* **WebSocket real-time notification**: when the Server manually updates an IP group, a subscription source sync succeeds, or security rules auto-trigger temporary bans, the Server immediately broadcasts the affected IP group update via WebSocket; the Agent lands it instantly — **no Nginx reload** throughout.
---
## Design Constraints
To protect the security boundary of the data and control plane, Agent development must strictly comply with the following engineering constraints:
To guarantee the security boundary of data and control channels, Agent code and secondary development must strictly follow:
1. **Zero-Privilege Command Execution**: The Server is strictly prohibited from sending any arbitrary shell commands or scripts to the Agent (such as exec/eval). All system control operations (such as start, stop, reload, update) must be hardcoded inside the Agent binary.
2. **Strict Token Filtering and Prefix Validation**: Agent requests to the Server must be prefixed with `/api/agent/` and must carry the `X-Agent-Token` header for signature or token verification.
3. **Node Autonomy**: The Agent must support complete offline capabilities. During disconnected periods, the local OpenResty must rely on local configuration copies to keep reverse proxy services running normally.
1. **Zero privileged command channel**: the Server is absolutely forbidden from passing arbitrary shell commands or remote script execution (exec/eval, etc.) to the Agent. All system control primitives (start, stop, reload, update) must be hardcoded inside the Agent binary.
2. **Strict Token filtering and prefix validation**: when the Agent requests resources from the Server, endpoints are fixed under the `/api/v1/agent/` prefix and must carry `X-Agent-Token` for signature/token verification.
3. **Node autonomy**: the Agent must have complete offline capability. While disconnected from the Server, the local OpenResty must keep reverse-proxying normally based on locally landed config.
4. **Observability reports facts only**: access logs are reported as details; host metrics report counters/instant readings. Computing conclusion metrics like business UV, Top domains, or 24h data provided inside the Agent is forbidden (the Server aggregates). See [Edge Observability & Business Traffic Stats](./observability-design.md).
5. **Pages consumes only control-plane artifacts**: Remote URLs, GitHub Releases, the auto scanner, and future repo checkout/build executors are all Server responsibilities. The Agent receives no external URLs, access tokens, repo credentials, or clone/install/build commands — it only pulls already-activated deployment packages with integrity metadata.
+149 -164
View File
@@ -1,223 +1,208 @@
# System Architecture
You will learn: The overall architecture of OpenFlare, the boundaries of responsibilities for Server, Agent, OpenResty, and Admin Frontend, and the request flow of a configuration publication from the admin dashboard to activation on a node.
You will learn: OpenFlare's overall architecture, the responsibility split of each core component (Server, Agent, OpenResty, Relay, Client), and the macro flow of the main data and request streams.
OpenFlare consists of the Server, the Agent, the node-local OpenResty, and the Admin Frontend. The Server is the control plane, the Agent is the only controlled entry point on the node side, and OpenResty serves as the actual data plane. In intranet penetration scenarios, the Relay (frps manager) and OpenFlared (frpc manager) extend the data plane traffic path.
OpenFlare is a self-hosted OpenResty control plane. Physically it consists of the Server (control plane), the Agent (config landing), node-local OpenResty (data plane), intranet penetration components (Relay and OpenFlared, data-plane extensions), and the admin frontend.
### Standard Reverse Proxy Traffic Path
---
## Traffic Path Overview
Depending on the website upstream type, OpenFlare supports three data-plane traffic paths:
### 1. Standard Reverse Proxy Path
```text
Browser
|
| Management UI / API
| HTTPS/HTTP request
v
OpenFlare Server (Gin + GORM + SQLite/PostgreSQL)
OpenResty (WAF, TLS, Rate Limit, optional origin error page)
|
| Agent API / heartbeat / config pull
| reverse proxy (proxy_pass)
v
OpenFlare Agent
|
| write config / openresty -t / reload / rollback
v
OpenResty binary
|
| reverse proxy
v
Origin
Origin Server (direct public/LAN upstream)
```
### Intranet Penetration Traffic Path
When the origin or gateway returns an error status in the configured list, a global custom/default HTML can be returned while keeping the real HTTP status; see [Origin Error Page Design](./origin-error-page.md).
### 2. Intranet Penetration Path
For origin services on firewall-restricted intranet servers:
```text
Browser
|
| HTTPS request
| HTTPS/HTTP request
v
OpenResty (Agent, TLS/WAF) <-- TunnelRelay Node
OpenResty (Agent host, TLS/WAF)
|
| proxy_pass http://localhost:vhost_port (Host header preserved)
v
OpenFlareRelay (frps) <-- TunnelRelay Node, co-located with Agent
OpenFlareRelay (frps) <-- same host as the Agent, provides relaying
|
| frp tunnel protocol (HTTP Vhost routing by Host header)
| frp tunnel protocol (Host header routing)
v
OpenFlared (frpc) <-- Intranet Server
OpenFlared (frpc) <-- firewall-restricted intranet server
|
| HTTP/HTTPS forward
v
Internal Service (192.168.x.x)
```
### 3. Pages Static Hosting Path
For pre-built SPAs or static site hosting:
```text
Browser
|
| HTTPS/HTTP request
v
OpenResty (Agent, TLS/WAF)
|
+---> [static serving] root/try_files ---> Agent local Pages deployment dir
|
+---> [API proxy] proxy_pass ---> backend API service (if API proxying enabled)
```
---
## Component Responsibilities
| Component | Responsibility |
| --- | --- |
| Server | Admin UI, Admin API, Agent/Relay/Client API, configuration rendering, version publishing, data storage, and aggregated queries. |
| Agent | Registration, heartbeats, synchronization, file writing, validation, reload, rollback on failure, self-updating, and light metrics collection. |
| OpenResty | Receives real traffic, executing WAF, PoW, authentication, and reverse proxying according to the configuration rendered by OpenFlare. |
| OpenFlareRelay | Manages the lifecycle of the frps process, providing tunnel relay services and receiving frps configurations via heartbeat. |
| OpenFlared | Manages frpc processes (can be multiple), connecting to the Relay and forwarding traffic to intranet services. |
| Frontend | Manages pages for website configs, WAF, origins, certificates, nodes, tunnels, versions, users, settings, and observability. |
| Component | Responsibility | Detailed Design Reference |
| --- | --- | --- |
| **Server** | admin UI/API, control-plane state persistence, config compilation/rendering, release versioning, Pages deployment package storage, Cloudflare A-record pointing, access-log storage and business traffic aggregation, Uptime Kuma monitoring sync, login CAPTCHA protection | [Agent & Publish Model](./agent-design.md) / [Cloudflare DNS Pointing Design](./cloudflare-pointing.md) / [Edge Observability & Business Traffic Stats](./observability-design.md) / [Uptime Kuma Sync Design](./kuma-design.md) / [Login CAPTCHA Design](./login-captcha.md) |
| **Agent** | periodic heartbeat & WS sync, static package pull/extraction, OpenResty config write/validate/reload and self-healing; observability reports only access details and host/health readings, no business pre-aggregation | [Agent & Publish Model](./agent-design.md) / [Edge Observability & Business Traffic Stats](./observability-design.md) |
| **OpenResty** | receives real traffic; executes WAF filtering, PoW protection, Basic Auth, static/reverse-proxy serving, and optional origin error pages | [WAF Design](./waf-design.md) / [Pages Design](./pages-design.md) / [Origin Error Page Design](./origin-error-page.md) |
| **Relay** | deployed on edge nodes; manages the `frps` daemon lifecycle and accepts heartbeat-dispatched penetration relay configs | [Tunnel Design](./tunnel-design.md) |
| **OpenFlared** | deployed in the intranet; manages the `frpc` process group, establishes reverse tunnels to multiple Relays, reports connection state | [Tunnel Design](./tunnel-design.md) |
## Server
---
`openflare-server` is the single-control-plane monolith:
## Component Architecture and Division
* Gin provides the HTTP services.
* GORM accesses SQLite or PostgreSQL.
* The existing login system provides Admin Session management.
* Authentication sources support GitHub OAuth and standard OIDC logins with external account binding.
* The Go Server hosts the `openflare-server/web` static build assets.
### 1. Server (control plane)
The Go backend at the repo root (module `github.com/Rain-kl/Wavelet`) is the OpenFlare control plane, built on the Wavelet full-stack scaffold:
* Provides admin REST APIs (`/api/v1/d/*`) authenticated via **Session Cookie**, with optional `X-Access-Token`.
* Edge node protocols go through `/api/v1/agent|relay|tunnel/*`, authenticated with `X-Agent-Token` / `X-Tunnel-Token` respectively.
* Contains the config Compiler, uniformly compiling DB rules, certs, and global params into immutable config snapshots and OpenResty physical config file text.
* Uniformly receives Pages local uploads, Remote URLs, and public GitHub Release pre-built artifacts, completing source checks, restricted downloads, archive validation, and immutable deployments; manual uploads create candidates awaiting explicit activation, persistent-source sync creates-or-loads and atomically activates. The Server offers controlled latest-download endpoints to Agents; the internal scanner handles limited GitHub latest checks, lease recovery, optional auto-publish, and orphan upload compensation; the generic task management entry can't modify this schedule. Future repo source builds are extended by a standalone Server build executor; the Agent never executes third-party fetch or build commands.
* Provides the optional Cloudflare DNS pointing control plane: maintains group desired state with ZoneDomains as members, idempotently syncing a single A record to the current active node IPv4 via Asynq; node IP changes only best-effort enqueue; no auto-failover in phase 1.
* Backend integration with the Uptime Kuma monitoring sync service auto-maintains HTTP probe tasks for available sites.
* Startup entry: root `main.go` + `internal/cmd/` (`api` / `worker` / `scheduler` / `all`); OpenFlare business in `internal/apps/openflare/`, edge protocol handling in `internal/apps/openflare/{agent,relay,flared}/`.
* *See: [Agent & Publish Model](./agent-design.md) and [Uptime Kuma Sync Design](./kuma-design.md)*
The Server does not directly SSH to nodes, nor does it modify node files online. It only stores control plane state, generates complete configuration versions, and lets nodes actively pull them via the Agent API.
### 2. Agent (config landing)
`openflare-agent` is the daemon running on the node:
* Maintains periodic heartbeats with the control plane after startup, receiving real-time config release broadcasts via the optional WebSocket.
* Pulls the latest active version's config files and certs, writes them locally, and performs safe validation via `openresty -t` before a smooth reload.
* Handles Pages deployment package download, SHA-256 validation, and extraction switching locally.
* *See: [Agent & Publish Model](./agent-design.md)*
## Agent
### 3. OpenResty (data plane)
Receives visitor traffic and performs final business landing:
* Traffic entry, supporting HTTP/2, HTTP/3 (QUIC), and dynamic TLS certificate binding.
* Embeds Lua logic filtering WAF rules and verifying PoW challenges efficiently in the `access_by_lua` phase, followed by connection/rate limits and basic caching (policy in [Edge Cache Strategy Design](./edge-cache-design.md)).
* *See: [WAF Design](./waf-design.md) and [Pages Static Hosting Design](./pages-design.md)*
`openflare-agent` is a Go monolithic application:
### 4. Relay and OpenFlared (tunnel components)
Extend data-plane reverse penetration:
* `openflare-relay` guards the local `frps`, accepts Server config dispatch, and auto-updates the relay port.
* `openflared` guards a group of `frpc` client processes in the intranet for nearest multi-relay connections and HA disaster recovery.
* *See: [Tunnel Design](./tunnel-design.md)*
* Runs as a single binary on the node side.
* Reads or generates local node information on startup.
* Performs periodic heartbeat check-ins to report status and retrieve active version summaries.
* Upon discovering a new version, it pulls the configuration, backs up old files, writes new files, validates them, and reloads.
* Automatically rolls back to restore operations if the application fails.
* Maintains the local WAF GeoIP mmdb, writing the built-in library on startup and updating it periodically based on configuration.
---
The Agent executes validation, reload, startup, and restart uniformly via the path specified in `openresty_path`; if unconfigured, it defaults to calling `openresty`. During Docker deployments, the Agent image packages OpenResty and follows the same execution control logic.
## Data and Request Flow Overview
The node IP is maintained by default through Agent registration and heartbeat reporting; if the administrator locks the node IP, the Server only updates running status, versions, and observability fields, and no longer accepts reports from the Agent to override the locked IP.
### 1. Config Release and Sync Flow
```text
admin modifies config -> release new version -> generate globally unique Checksum active version
|
+------------------+------------------+
| (WebSocket broadcast or periodic Heartbeat) |
v v
[edge node Agent] [intranet OpenFlared]
pull latest OpenResty config/certs pull latest Tunnel mapping config
incrementally pull/extract Pages packages generate/rewrite frpc.toml
validate config and smooth reload smooth reload or spawn frpc
report apply state (Success / Error) report tunnel connection state and metrics
```
* *Fine-grained sync/self-healing timing and the rollback model: [Agent & Publish Model](./agent-design.md)*
## Frontend
### 2. Static Hosting and API Proxy Flow
* Static assets are extracted to `projects/{project_id}/current` on the Agent node (pulled per project latest, only the newest package kept); OpenResty serves static resources at the edge via `root`/`index`/`try_files`.
* With API proxying enabled, OpenResty rewrites and forwards (`proxy_pass`) API requests to the backend dynamic API based on the site's `api_proxy_path` (e.g. `/api`).
* Admin operations and the internal scanner only generate constrained artifact candidates, reusing the unified inspect, `upload.Ingest`, and deployment pipeline. Manual uploads create a new inactive candidate; persistent-source sync/scanner creates-or-loads and atomically activates. A future repository build executor can only emit into the same artifact pipeline; the Agent is always just an active-deployment consumer.
* *Package validation, extraction escape defense, and Nginx rule rendering: [Pages Static Hosting Design](./pages-design.md)*
`openflare-server/web` is the official Next.js-based frontend:
### 3. WAF Security Filtering Flow
* The WAF engine is embedded in the OpenResty request lifecycle.
* WAF rules are orchestrated as a visual DAG on the control plane and compiled into a runtime graph at release; after an OpenResty reload each Worker loads it once, and subsequent requests only traverse the in-memory object.
* Global rules always run first; route-bound rules execute in explicit order; reaching "pass" in the current rule continues to the next, reaching "block" immediately returns that node's configured block response.
* IP group members hot-update independently: a coordinating worker checks the checksum every 5 seconds, loading the full snapshot only on change; each Worker's request path always reads the local in-memory object.
* *IP group sources and sync: [WAF Design](./waf-design.md); graph model, execution semantics, release constraints: [WAF Orchestration Rule Design](./waf-orchestration-design.md)*
* Next.js 15 App Router.
* React 19.
* TypeScript.
* Tailwind CSS.
* TanStack Query for server-side state.
### 4. Edge Observability and Business Traffic Stats Flow
```text
OpenResty access.log (business facts)
|
| Agent tails incremental details (no sum/count/uniq)
v
Server stores via logstore (current log primary DB: PostgreSQL / SQLite / ClickHouse)
|
+---> global aggregation --> dashboard "data provided / requests / UV"
+---> host∈Zone --> Zone "data provided" etc. (same semantics)
+---> node_id filter --> node business volume
The frontend uses static export mode (`output: 'export'`), which is then hosted by the Go Server using `embed.FS`. All API requests must go through `lib/api/` and process the `success/message/data` response structure.
host /proc NIC, CPU etc. --> Agent reading snapshots --> host resource trends (displayed separately from business delivery)
OpenResty health and connections --> edge health (instant, not 24h business totals)
```
* **Principle**: the Agent reports only facts; the Server interprets facts; access logs are the single truth for business traffic. `openresty_tx` and "data provided" must not run on dual tracks.
* *Transport model, examples, and collection frequency: [Observability Transport Model](./observability-transport-model.md); field convergence and migration: [Edge Observability & Business Traffic Stats](./observability-design.md)*
The Server integrates the following security features:
* CORS middleware: Cross-Origin Resource Sharing protection.
* Rate limiting: Global and key API endpoint throttling.
* Session management: Cookie/Redis-based session storage.
## Data & Request Flow
### Management Request Flow
### 5. Cloudflare DNS Pointing Flow
```text
Browser -> Frontend -> /api/* -> controller -> service -> model -> database
admin configures connection/group/member -> Server persists desired state -> Asynq sync tasks
|
v
Cloudflare Zone / DNS API
|
v
single A record -> active_node IPv4
node IP manually updated or Agent heartbeat change --------------------> best-effort enqueue per node
```
Admin mutation APIs use `POST`, while read-only APIs use `GET`. Both success and failure responses return a clear `message`.
* The Cloudflare module only manages cached or taken-over uniquely-named A records; it doesn't extend the Zone core into an authoritative DNS control plane. On multiple same-name A records it stops syncing and asks the admin to clean up in Cloudflare.
* Group backup/active nodes are reserved for later failover; phase 1 fixes the primary node and doesn't auto-switch on heartbeat offline.
* *Connection, model, idempotent sync, and phasing: [Cloudflare DNS Pointing Design](./cloudflare-pointing.md)*
### Agent Sync Flow
```text
Agent HTTP heartbeat -> Server returns active version summary
Agent detects new version -> Pulls complete configuration details
Agent writes main configuration / route configurations / certificates / Lua resources / WAF runtimes
Agent runs OpenResty validation (openresty -t) and reload
Agent reports application result
```
### Relay Sync Flow
The Relay (OpenFlareRelay process) runs on the TunnelRelay node and shares the same `agent_token` with the Agent:
```text
Relay HTTP heartbeat -> Server returns frps base configuration (bindPort, vhostHTTPPort, auth_token)
Relay generates frps.toml and starts or updates the frps process
Relay periodically reports frps health status and connection statistics
Relay attempts WebSocket upgrade for real-time configuration pushes
```
frps configurations are relatively static (ports, auth token), dispatched via heartbeats, and **not included in the versioned publishing flow**. The Relay must monitor the frps process and auto-recover it on failures. Authentication: `X-Agent-Token` + API path prefix `/api/relay/*`, distinguished by Server via `node_type = tunnel_relay`.
### OpenFlared Sync Flow
OpenFlared (client) runs inside the intranet server, using independent `tunnel_token` authentication:
```text
Client HTTP heartbeat -> Server returns tunnel configuration version summary (version, checksum)
Client detects new version -> Pulls complete tunnel route configuration (relay list + frpc proxy definitions)
Client generates independent frpc.toml configuration files for each Relay
Client starts a new frpc process for new Relays, or hot-reloads (frpc reload) existing ones
Client reports application results (success/failure details)
```
OpenFlared communicates with the Server via `/api/flared/*` using the `X-Tunnel-Token` header. Tunnel route configurations are versioned along with the publishing flow, ensuring all configuration changes are consistently published to both Agents and Clients via a single version number.
**WebSocket Upgrade Flow** (Optional, controlled via `AgentWebsocketUpgradeEnabled`):
When WebSocket upgrade is enabled:
1. The Agent retrieves run configurations and settings via HTTP heartbeat.
2. The Agent attempts to upgrade the connection to `GET /api/agent/ws` (WebSocket).
3. Once the WS connection is established, periodic state reporting and real-time commands are carried over the WebSocket pipeline, minimizing latency.
4. When the Server publishes or activates a version, it immediately broadcasts the active version summary to connected Agents, triggering the sync flow instantly.
5. If the WebSocket disconnects or fails to establish, the Agent automatically falls back to HTTP heartbeats, ensuring high availability.
Through the `OpenRestyWebsocketEnabled` option, WebSocket reverse proxy support can be enabled or disabled at the OpenResty layer.
### Reverse Proxy Flow
```text
Client -> OpenResty server block -> WAF Lua -> named upstream -> Origin
```
Website configurations are the boundaries of reverse proxy aggregation. A single website configuration can bind multiple domains, sharing site-level rate limiting, reverse proxy, and cache settings.
WAF executes in the OpenResty `access_by_lua_file` phase. Rules originate from the `waf_config.json` carried in the currently active version; global rule groups take effect by default, and websites can overlay custom rule groups. `waf_config.json` only stores rule group references and IP group IDs; IP group members are synchronized independently by the Agent into `waf_ip_groups.json`, and the OpenResty Lua engine merges and evaluates them by reference ID.
WAF IP groups are managed by the Server. Manual IP groups store IP/CIDR lists directly; auto IP groups are evaluated by Server cron jobs reading request logs and applying Expr boolean rules; subscription IP groups are fetched by Server cron jobs from remote text or JSON sources. The Agent reports local IP group checksums in heartbeats, and the Server only returns mismatched IP groups. When an IP group is updated on the Server, a broadcast is sent via WebSocket to push changes, and the OpenResty Lua reads the local JSON file directly without querying the DB, request logs, or remote subscription sources.
---
## Core Objects
Current valid entities include:
Current core system entities include:
* `proxy_routes`
* `origins`
* `config_versions`
* `nodes`
* `tunnels`
* `auth_sources`
* `external_accounts`
* `node_system_profiles`
* `apply_logs`
* `tls_certificates`
* `managed_domains`
* `node_request_reports`
* `node_access_logs`
* `node_metric_snapshots`
* `traffic_analytics_rollups`
* `node_health_events`
* `waf_rule_groups`
* `waf_ip_groups`
* `waf_rule_group_bindings`
* `acme_accounts`
* `dns_accounts`
* `geoip_update_configs`
* **Reverse proxy & config**: `zones` (root-domain management boundary), `zone_domains` (explicit domains with cert/route association), `proxy_routes` (route policy), `origins`, `config_versions`, `tls_certificates`. See [Zone & Domain Resource Design](./zone-design.md).
* **Cloudflare DNS pointing**: `of_cf_connections` (global connection), `of_cf_pointing_groups` (primary/backup/active nodes and default orange-cloud), `of_cf_pointing_members` (ZoneDomain members, record cache, sync state). See [Cloudflare DNS Pointing Design](./cloudflare-pointing.md).
* **Pages static hosting**: `of_pages_projects`, `of_pages_project_sources` / `of_pages_project_source_runtime` (mutable source config and runtime), `of_pages_deployments` (immutable deployments), `of_pages_deployment_files` (deployment file manifests).
* **Nodes & tunnels**: `nodes`, `tunnels` (tunnel clients), `node_system_profiles`, `apply_logs`.
* **WAF & security**: `waf_rule_groups`, `waf_ip_groups`, `waf_rule_group_bindings` (site WAF bindings).
* **System & accounts**: `acme_accounts`, `dns_accounts`, `geoip_update_configs`.
---
## Key Design Decisions
| Decision | Rationale |
| Decision | Reason |
| --- | --- |
| Full Config Versioning instead of Patches | Provides stable, verifiable boundaries for previewing, activating, history, and rollbacks. |
| Pull Model (Agent-driven) | Server does not need SSH keys or inbound command ports, preventing control channel hijacking. Supports HTTP and WebSocket. |
| Global Single Active Version | Reduces MVP complexity, ensuring all nodes are uniform by default. Supports previews, version history, and one-click rollback. |
| Website Multi-Domain Aggregation | Enables sharing site-level policies across domains while supporting per-domain certificate binding. |
| Server-side Observability Aggregation | Prevents UI-side temporary statistical calculations from producing inconsistent data metrics. |
| Intranet Penetration based on frp | Reuses a mature tunnel protocol rather than custom implementations to minimize stability risks. frps Vhost routing aligns naturally with HTTP. |
| Independent Binary for Relay/Client | Separation of concerns: Relay manages frps, Client manages frpc, allowing independent updates and deployments. |
| Tunnel decoupled from Node system | Tunnel clients run internally, using completely different registration and authentication flows compared to edge nodes. |
| Full config versions instead of online patching | stable boundaries for preview, activation, history, and rollback; consistent node state |
| Agent active pull | the Server needs no SSH access, lowering security risk; supports HTTP/WebSocket dual-protocol switching |
| Globally single active version | lowers control-plane complexity, keeps all nodes consistent by default; stable one-click second-level rollback |
| Zone domains separated from route policy | Zones provide the root-domain entry and domain boundaries; routes still reuse the same site-level policy and bind certs per domain |
| Cloudflare pointing independent of the Zone core | ZoneDomains only provide explicit FQDNs; the Cloudflare module drives single A records from DB desired state without widening Zones into a general DNS control plane |
| Intranet penetration integrated on frp | reuses a mature tunnel protocol, avoiding self-built tunnel stability risks; its Vhost mechanism natively fits reverse-proxy routes |
| Runtime config decoupled from the control store | WAF rules compile at release and load with the OpenResty reload; dynamic IP groups refresh independently via checksum-driven memory snapshots |
| Access logs as the single truth for business traffic | the Agent forbids business pre-aggregation; dashboard and Zone share Server-side aggregation, avoiding openresty_tx vs bytes_sent dual tracks |
| Business delivery / edge health / host capacity layered | data provided ≠ host NIC outbound ≠ OpenResty connections; UI and API name and section them separately |
| Pages artifacts separated from repo builds | current sources only import pre-built artifacts; future checkout/build happens in a Server-isolated executor reusing the artifact pipeline; the Agent never runs third-party builds |
## Recommended Reading for Contributors
Before modifying architectural code, please read:
1. [Product Boundaries](./index.md)
2. [Agent & Publish Model](./agent-design.md)
3. [Development Constraints](../../guideline/Constraints.md)
4. [Repository Structure](./repository.md)
---
+161 -174
View File
@@ -1,204 +1,191 @@
# Product Boundaries
You will learn: What OpenFlare is, what problems it solves, who the target audience is, what current stable features are available, and which design boundaries cannot be bypassed during implementation.
You will learn: what OpenFlare is, its current stable capabilities, and the core product boundaries and repository structure layout you must follow when developing.
OpenFlare is a self-hosted OpenResty control plane designed for single-team or single-organization internal operations. It solves the problems of decentralized management of reverse proxy configurations, node synchronization, certificate hosting, configuration publication and rollback, and basic observability.
OpenFlare is a self-hosted OpenResty control plane for single-team or single-organization internal operations.
---
## Project Positioning
OpenFlare is suitable for teams that need to centrally manage multiple OpenResty proxy nodes:
OpenFlare suits teams that need to centrally manage multiple OpenResty proxy nodes, with this positioning:
* **Control/landing separation**: the Server control plane doesn't SSH into proxy nodes; Agents actively pull versions and apply them.
* **Immutable config release**: full config versions are used for preview, release, activation, and one-click rollback.
* **Integrated gateway hosting**: website reverse proxying, automatic TLS certificate issuance/renewal, WAF protection, intranet penetration (Tunnel), and Pages static hosting are integrated into one control plane.
* Wanting to maintain reverse proxy website configurations using a management dashboard.
* Wanting every configuration change to have a complete version history, preview, activation, and rollback support.
* Wanting nodes to actively synchronize configurations, rather than the control plane SSHing into nodes to execute commands.
* Wanting to manage TLS certificates, domain assets, node statuses, and basic access analytics in a single system.
**Not this product's positioning**: multi-tenant cloud platforms, Kubernetes Ingress Controllers, service meshes, or general log platforms.
OpenFlare is currently not positioned as a general-purpose logging platform, service mesh, Kubernetes Ingress Controller, or multi-tenant cloud platform.
---
## Current Capabilities
| Capability | Description |
| Capability | Description | Detailed Design/Usage |
| --- | --- | --- |
| **Reverse proxy config management** | website rules (Proxy Route) as the aggregation boundary; multi-domain and multi-upstream load balancing | [Create a Reverse Proxy Config](../guide/proxy-config.md) |
| **Origin error page** | globally configurable: matching origin/gateway status codes return OpenFlare default or custom HTML with the HTTP status kept | [Origin Error Page Design](./origin-error-page.md) |
| **Edge cache** | single-node OpenResty `proxy_cache`; default static extensions + origin-header/Set-Cookie gates + default Edge TTL (benchmarked to the CF default model) | [Edge Cache Strategy Design](./edge-cache-design.md) |
| **Zone & domain management** | registrable root domains as the management entry, aggregating explicit domains, domain certificates, and reverse proxy routes | [Zone & Domain Resource Design](./zone-design.md) |
| **Cloudflare DNS pointing** | per ZoneDomain, idempotently point a single Cloudflare A record at an edge node IPv4; connection config, groups, member orange-cloud, and async sync; no auto-failover in phase 1 | [Cloudflare DNS Pointing Design](./cloudflare-pointing.md) |
| **Config versioning** | global single active version with preview, release, immutable snapshot history, and second-level one-click rollback | [Agent & Publish Model](./agent-design.md) |
| **WAF protection** | visual DAG rule orchestration, manual/auto/subscription IP groups, GeoIP matching, and PoW CC protection | [WAF Design](./waf-design.md) / [WAF Orchestration Rule Design](./waf-orchestration-design.md) / [WAF Usage Guide](../guide/waf-usage.md) |
| **Intranet penetration** | reverse-penetrate and expose intranet web services via Relay nodes and the OpenFlared client | [Tunnel Design](./tunnel-design.md) / [Tunnel Usage Guide](../guide/tunnel-usage.md) |
| **Pages static hosting** | upload or sync pre-built artifacts from Remote URLs or public GitHub Releases; GitHub latest can be periodically checked and optionally auto-published. Immutable deployments are pulled by edge nodes and served locally by OpenResty, supporting rollback, API proxying, and SPA Fallback | [Pages Static Hosting Design](./pages-design.md) / [Pages Usage Guide](../guide/pages-usage.md) |
| **TLS certificate auto-renewal** | explicitly bind certificates to Zone domains; issue/renew via ACME against Let's Encrypt | [Zone & Domain Resource Design](./zone-design.md) |
| **Multi-node monitoring & observability** | access logs as the single truth for business traffic; Agent reports only details and host readings, Server aggregates uniformly; reconciled with Zone/dashboard | [Observability Transport Model](./observability-transport-model.md) / [Edge Observability & Business Traffic Stats](./observability-design.md) / [Reporting Protocol & Tables](./observability-data-model.md) / [System Architecture](./architecture.md) |
| **Log storage** | access logs and observability time series use the switchable log primary DB (follows the business primary DB or ClickHouse); still writable/queryable with ClickHouse off | [Log Store Decoupling](./logstore.md) |
| **Console bilingual** | zh-CN / en without URL prefixes, `NEXT_LOCALE` cookie precedence, static-export compatible | [Frontend i18n design](../superpowers/specs/2026-07-24-frontend-i18n-design.md) |
---
## Core Product Boundaries and Constraints
When developing and contributing code, **you must strictly follow** these business boundaries and technical constraints; don't bypass them for temporary needs:
### 1. Website Config and Upstream Constraints
* **Single-site domain sharing policy**: one route rule corresponds to one website; the site's multiple domains share rate limit, cache, and reverse-proxy upstream config. Differential per-domain service config within the same rule is not supported.
* **Upstream type mutual exclusion**: the upstream must be one of direct address (`direct`), intranet tunnel (`tunnel`), or Pages static hosting (`pages`); mixing within one rule is not allowed.
* **Direct type restrictions**: a direct upstream can be a single or multiple pure `http://` or `https://` addresses (multi-address only supports plain `scheme://host[:port]`); non-HTTP protocols (TCP/UDP) upstreams are not supported.
### 2. WAF Security Boundaries
* **Allowlist priority**: the allowlist has absolute matching power. Only when an allowlist rule isn't hit do the global and custom blocklist filters trigger in order.
* **GeoIP weak dependency**: geo access resolution fully depends on the node-local MaxMind DB. When GeoIP is abnormal or fails to resolve, the system must auto-ignore geo rules — **never** break IP-group filtering or the reverse-proxy main chain's availability.
* **Runtime data decoupling**: OpenResty interception only reads Agent-synced local JSON, never talking to the Server DB. IP group member sync is decoupled from version release via Checksum differential pull for zero-reload smooth effect.
### 3. Intranet Penetration Boundaries
* **HTTP traffic only**: the tunnel components only support HTTP/HTTPS (based on frp's vhost mechanism for single-port domain-route reuse); standalone TCP/UDP port allocation is not supported yet.
* **Dynamic relay config control**: a Relay node, after connecting to the Server, dynamically pulls and syncs global system config via heartbeats (e.g. whether the embedded FRPS Web UI and its port are enabled), but isn't part of the control plane's immutable config version release system.
* **Tunnel/Node system isolation**: Tunnel clients make outbound connections from the intranet and are independent entities from control-plane-hosted edge Nodes (public nodes), authenticated with the dedicated `tunnel_token`.
### 4. Pages Static Hosting Boundaries
* **Pre-built artifact sources**: a project may stay manual-upload, or configure one Remote URL / public GitHub Release asset source. Remote and fixed tags only support manual ops; only GitHub latest enters scheduled checks and can opt into auto-update. Sources are switchable, but immutable deployments and the current production version don't get lost when editing or deleting a source.
* **Archive and resource limits**: supports `zip`, `tar.gz` / `tgz`, `tar.xz` / `txz`, `tar.bz2` / `tbz2`, `tar`, `7z`. Archive cap controlled by `pages_max_package_size_mb` (default 100 MiB, range 1–2048); expanded single-file and total limits are 4× the package cap with a 100 MiB floor, at most 1,000 regular files. Both Server and Agent validate actual bytes and reject path traversal, symlinks/hard links, and special files.
* **Build and runtime boundaries**: currently no source checkout or build execution from external git repos, and no edge Serverless, dynamic SSR, or preview subdomains. Future repo integration must use a separate `git_repository` Provider with a Server-side isolated build executor, emitting only restricted artifacts into the unified artifact pipeline; the Agent never receives repo credentials, external URLs, or clone/install/build commands.
### 5. System and Version Boundaries
* **Globally single active version**: all nodes pull and consume the same globally active config. Per-node-group differentiated config release isn't performed.
* **Single-tenant architecture**: OpenFlare is for a single team deploying on a trusted internal network. Single-tenant by design; fine-grained multi-user roles or multi-tenant resource isolation aren't supported.
* **External infra dependency**: the Server **must depend on** external Redis (or Valkey) for distributed coordination, the Asynq queue, and system cache. The relational DB is PostgreSQL, or SQLite when `database.enabled` is off. ClickHouse **optional**: when off, access logs and observability time series are handled by the current log primary DB (follows the business primary DB); when on, the「Switch Log Database」task can migrate to ClickHouse. Running without Redis is not supported. See [Log Store Decoupling](./logstore.md).
---
## Repository Structure
OpenFlare has converged to a **single monorepo** (Go module `github.com/Rain-kl/Wavelet`). The control-plane Server and edge components (Agent, Relay, OpenFlared) share the repo, organized by Wavelet `internal/apps/` domain modules.
When contributing code, strictly follow this physical layering and directory division:
| Path | Responsibility |
| --- | --- |
| Reverse Proxy Rules | Uses website configuration as the aggregation boundary, supporting multiple domains and origin settings. |
| Website-level Config | One rule corresponds to one website, which can bind one or more domains and share site-level configurations. |
| Origin Management | Maintains a lightweight origin directory and allows websites to save renderable origin snapshots. |
| Config Versioning | Supports previews, publishing, activation, immutable history, and rollbacks. |
| Agent Sync | Supports registration, heartbeats, synchronization, application result reporting, and self-updating. |
| OpenResty Hosting | Manages main config templates, performance parameters, cache parameters, and Lua resources. |
| HTTPS/TLS | Hosts certificate and domain assets, binding certificates on a per-domain basis. |
| WAF | Maintains IP/CIDR block blacklists/whitelists, IP groups, and country-level geographic access controls at both global and site-specific levels. |
| Basic Observability | Aggregates node requests, resource snapshots, health events, and access analytics. |
| Node Management | Manages node status, token systems, and deployment/update lifecycles. |
| Admin UI | Next.js-based official management dashboard. |
| Auth Source Login | Supports configuring GitHub OAuth and standard OIDC login portals, allowing third-party accounts to bind to existing local users. |
| Intranet Penetration | Securely exposes intranet HTTP services to the public internet using TunnelRelay nodes and the OpenFlared client, reusing the Agent's HTTPS/WAF capabilities. |
| `main.go` | the Server's single entry, delegating to `internal/cmd/` |
| `cmd/agent`, `cmd/relay`, `cmd/flared` | edge component CLI entries (**not** the Server) |
| `internal/` | control-plane and edge runtime implementations |
| `frontend/` | Next.js admin panel; build artifacts embedded into the Go Server |
| `pkg/` | cross-component shared libs (protocol, rendering, GeoIP, etc.) |
| `scripts/` | Swagger generation, install scripts, etc. |
| `docs/` | VitePress docs site and design baseline |
| `docker/` | per-component Dockerfiles |
| `uploads/`, `data/` | runtime upload dir and static data (`.gitignore`d) |
Default Working Model:
### 1. Server Layering (`main.go` + `internal/`)
* All nodes consume the same globally activated configuration version.
* The Server stores configurations and state, and does not directly SSH to manage nodes.
* The Agent is the only controlled entry point on the node side.
* TunnelRelay nodes run both the Agent (OpenResty) and the Relay (frps manager) to provide intranet penetration relays.
* The OpenFlared client runs inside the intranet, managing the frpc process to connect to the Relay and forward traffic to intranet services.
## Typical Use Cases
| Scenario | Description |
| Directory | Responsibility |
| --- | --- |
| Unified Entrance | Exposes multiple internal HTTP services via a unified domain and TLS certificate. |
| Multi-Node Sync | Multiple OpenResty nodes consume the same active configuration version. |
| Change Review | View previews or diffs before publishing, keeping an immutable history post-publish. |
| Rapid Rollback | Re-activate an older version, letting the Agent pull and apply it. |
| Certificate Hosting | Bind TLS certificates to different domains under the same website. |
| Observability | Check node health status, aggregated requests, traffic analytics, and health events. |
| Intranet Penetration | Exposes intranet HTTP services that are not directly reachable from the public internet using Tunnels, benefiting from HTTPS, WAF, and all other protections. |
| `main.go` | Server startup entry |
| `internal/cmd/` | Cobra subcommands: `api`, `worker`, `scheduler`, `all` (default fused mode) |
| `internal/platform/bootstrap/` | cross-module assembly: task handlers, push domain events, process-level init |
| `internal/router/` | HTTP route registration and global middleware |
| `internal/router/v1/openflare/` | OpenFlare route registrars (`register_*.go`) |
| `internal/apps/openflare/` | OpenFlare control-plane business domains (`routers.go` + `logics.go`) |
| `internal/apps/{admin,user,oauth,upload,cap,...}/` | Wavelet platform capabilities (users, auth, tasks, push, etc.) |
| `internal/apps/openflare/{agent,relay,flared}/` | **Server-side** edge protocol handlers (auth, heartbeat, WS) |
| `internal/model/` | GORM entities / DTOs / no-IO domain rules (`openflare_*.go` + platform models); **no** DB access |
| `internal/infra/persistence/migrator/goose/` | goose SQL migrations (PostgreSQL / SQLite / ClickHouse) |
| `internal/repository/` | data access layer (platform + OpenFlare business CRUD, cache, `logstore` log IO); the **only** persistence entry |
| `internal/infra/task/` | Asynq async tasks (Worker + Scheduler) |
| `internal/infra/config/` | Viper config loading |
| `internal/shared/` | unified API response wrapper (`response/`) |
| `pkg/protocol/` | Relay / Tunnel shared HTTP/WS protocol structures |
| `pkg/render/`, `pkg/geoip/`, `pkg/wsclient/` | OpenResty config rendering, GeoIP, WebSocket client |
## Website Configuration Constraints
**API route prefixes:**
`proxy_routes` is the aggregate object for "website configurations". One record corresponds to one website, which can bind one or more domains and share a set of site-level configurations.
| Prefix | Purpose | Auth |
| --- | --- | --- |
| `/api/v1/d/*` | OpenFlare admin console API | Session Cookie + optional `X-Access-Token` |
| `/api/v1/agent/*` | Agent node protocol | `X-Agent-Token` |
| `/api/v1/relay/*` | Relay protocol | `X-Agent-Token` |
| `/api/v1/tunnel/*` | Tunnel client protocol | `X-Tunnel-Token` |
| `/api/v1/admin/*` | Wavelet platform admin API | admin Session |
Constraints:
### 2. Agent Modules (`internal/apps/agent/` / `cmd/agent/`)
| `internal/apps/agent/httpclient/` | Server communication |
| `internal/apps/agent/wsclient/` | WebSocket client communication |
| `internal/apps/agent/protocol/` | Agent API protocol types |
| `internal/apps/agent/updater/` | Agent self-update logic |
| `internal/apps/agent/logging/` | logging |
| `internal/apps/agent/observability/`| observability (metrics, traces, etc.) |
| `internal/apps/agent/geoipdata/` | GeoIP data handling |
| `internal/apps/agent/geoipupdate/` | GeoIP data updates |
| `internal/apps/agent/agent/` | core Agent logic and lifecycle |
* `proxy_routes.site_name` is the unique business identifier of the website.
* `proxy_routes.domains` must contain at least one domain, and `domains[0]` is treated as the primary domain.
* Any domain can globally belong to only one `proxy_routes`.
* Site-level rate limits, reverse proxies, and caching configurations are shared by the site, with no per-domain differences allowed within the same website.
* HTTPS allows binding certificates on a per-domain basis within the same site.
### 3. Frontend Layering (`frontend/`)
## Origin & Upstream Constraints
Based on the Wavelet Next.js scaffold, OpenFlare business UI is organized route-co-located under `app/(main)/`.
`origins` serve the reuse of the origin directory, storing only the origin address, display name, and remarks, without carrying protocols, ports, paths, weights, or health check policies. `proxy_routes` can optionally associate with an `origins` record, but the rule internally still saves a complete upstream snapshot for rendering.
| Directory | Responsibility |
| --- | --- |
| `app/` | Next.js App Router; `(main)` console, `(auth)` auth, `(docs)` docs pages |
| `app/(main)/<domain>/` | business pages and in-domain components (route-co-located) |
| `components/` | cross-domain reusable UI (`ui/`, `layout/`, `common/`, etc.) |
| `lib/services/` | API service layer: `core/` base class + `openflare/` business APIs |
| `lib/navigation/` | OpenFlare sidebar nav config (`openflare-nav.ts`) |
| `lib/theme/` | theme parsing and switching |
| `contexts/` | cross-page UI state (user, notifications, etc.) |
| `hooks/`, `lib/hooks/` | reusable React Hooks |
| `public/` | static assets and theme CSS |
| `scripts/` | build helper scripts |
| `proxy.ts` | dev/prod proxy: API rate limit and page auth |
Upstream Constraints:
**API conventions**: OpenFlare business APIs uniformly prefix `/api/v1/d/*`, wrapped via `OpenFlareBaseService`; page data fetching uses `@tanstack/react-query`.
* `proxy_routes` must contain at least one upstream address (for direct type `direct`), or be associated with a Tunnel (for intranet penetration type `tunnel`).
* Multi-upstream load balancing is uniformly rendered into a named `upstream` with keepalive enabled.
* A single upstream is allowed to carry a base path or query, which is appended in `proxy_pass`. Multi-upstream is strictly limited to pure `scheme://host[:port]` structures, and all upstreams in the same rule must use the same protocol.
* `proxy_routes.origin_host` is an optional field used to override the `Host` header during back-to-source requests.
* All direct upstream addresses must be valid `http://` or `https://` URLs.
* Intranet penetration upstreams must associate with a valid `tunnel_id` and specify the intranet target address and protocol.
### 4. Relay Modules (`internal/apps/relay/` / `cmd/relay/`)
## Intranet Penetration Constraints
| Module | Responsibility |
| --- | --- |
| `cmd/relay/` | Relay CLI entry and init main |
| `internal/apps/relay/config/` | local config parsing and default init |
| `internal/apps/relay/frps/` | manage frps process lifecycle, ports & Token, monitor runtime |
| `internal/apps/relay/heartbeat/` | periodic HTTP heartbeat, report state, fetch update requests |
| `internal/apps/relay/httpclient/` | generic Server API client helpers |
| `internal/apps/relay/observability/` | collect local host and frps base runtime metrics with pre-aggregation |
| `internal/apps/relay/relay/` | coordinate core lifecycle, init, and cleanup |
| `internal/apps/relay/state/` | local runtime state, error records, persistent cache |
| `internal/apps/relay/updater/` | Relay upgrade check, download/install, restart |
| `internal/apps/relay/wsclient/` | long-lived WebSocket bidirectional channel with the Server |
OpenFlare implements intranet penetration through TunnelRelay nodes and the OpenFlared client, built on top of frp (Fast Reverse Proxy).
### 5. OpenFlared (Client) Modules (`internal/apps/flared/` / `cmd/flared/`)
### Node & Component Model
| Module | Responsibility |
| --- | --- |
| `cmd/flared/` | Client CLI entry and init main |
| `internal/apps/flared/config/` | local client config loading and parsing |
| `internal/apps/flared/flared/` | intranet penetration client core scheduling and state management |
| `internal/apps/flared/frpc/` | hot-reload/dynamically generate per-Relay `frpc_{relayNodeID}.toml` and monitor frpc |
| `internal/apps/flared/heartbeat/` | heartbeat communication with the control plane, incl. Token validation |
| `internal/apps/flared/httpclient/` | generic client API communication (`/api/v1/tunnel/*`) |
| `internal/apps/flared/sync/` | incrementally pull latest Tunnel route bindings, generate snapshots, apply |
| `internal/apps/flared/updater/` | client self-update, new-version check, update landing |
| `internal/apps/flared/wsclient/` | WS channel for real-time Server tunnel config change push |
**Node Types**:
> **Note**: OpenFlared has no standalone `state/` package; version and checksum are persisted by `frpc/manager.go` to `flared-state.json`.
* `nodes.node_type` distinguishes the node type: `edge_node` (edge node, default) and `tunnel_relay` (tunnel relay).
* TunnelRelay nodes run both the Agent (OpenResty) and the Relay (frps manager) concurrently, sharing the same `agent_token`.
- The Agent is responsible for HTTPS termination, WAF protection, caching, and rate limiting.
- The Relay manages the frps process, providing tunnel relay services for intranet clients.
* TunnelRelay nodes introduce new fields: `node_type`, `relay_bind_port` (frpc connection port, default 7000), `relay_vhost_http_port` (HTTP Vhost port, default 8080), `relay_auth_token` (automatically generated), `relay_status`, etc.
---
**Tunnel Client**:
## Doc Maintenance Principles
* The `tunnels` table independently stores intranet penetration client registration info and is decoupled from the `nodes` system.
* Each Tunnel has a unique `tunnel_id` (format `tun-<32hex>`) and `tunnel_token` (client authentication credential).
* The OpenFlared client runs inside the intranet, is not exposed to the public internet, uses `tunnel_token` for authentication, and communicates with the Server via `/api/flared/*` endpoints.
* An OpenFlared client can connect to multiple Relays simultaneously for high availability.
### Upstream Type Expansion
The upstream configuration of `proxy_routes` is divided into two types, distinguished by the `upstream_type` field:
* **Direct Upstream (`direct`, default)**: Forwards traffic directly to the origin address, behaving exactly like the existing mechanism.
* **Intranet Penetration Upstream (`tunnel`)**: Forwards traffic to the intranet service via a TunnelRelay node.
- Must specify `tunnel_id` (associated with the `tunnels` table).
- Must specify `tunnel_target_addr` (intranet target address, e.g., `192.168.1.100:8080`) and `tunnel_target_protocol` (`http` or `https`).
- During publication, the Server automatically replaces the upstream address with `http://127.0.0.1:{relay_vhost_http_port}`.
### Traffic Paths & Protocols
**Complete Data Plane Traffic Path**:
```
Browser → OpenResty (Agent, TLS/WAF) [TunnelRelay Node]
↓
frps (Relay, HTTP Vhost Routing) [TunnelRelay Node, 127.0.0.1:{vhost_port}]
↓
frp Tunnel Protocol (Host Header Routing)
↓
frpc (Client, Multi-process) [Intranet Server]
↓
Intranet Service (192.168.x.x:port)
```
**Key Features**:
* frps uses the HTTP Vhost single-port reuse mechanism; all HTTP tunnels share one `vhost_port`, automatically routed to the corresponding frpc based on the Host header.
* The Agent preserves the original `Host` header, which frps uses to match the virtual host.
* Each tunnel corresponds to a single `proxy_routes` and can bind multiple domains.
* The OpenFlared client manages an independent frpc process for each connected Relay, transmitting multiple HTTP proxy definitions via a single frp tunnel.
### Configuration Sync Model
The publication process generates two types of configuration version data simultaneously, linked by a single `config_version` version number:
* **Agent-side Config**: OpenResty main configuration + route configurations + WAF rules. If a tunnel upstream is included, it is automatically rendered as a `http://127.0.0.1:{vhost_port}` upstream.
* **Tunnel-side Config**: Relay list + frpc proxy definitions. Versioned alongside the publishing process; changes are hot-reloaded using `frpc reload` first.
* **Relay Config**: Dispatched via heartbeat responses, relatively static, and not included in the versioned publishing flow.
### Tunnel Design Constraints
* Only HTTP protocol tunnel traffic is supported (keeping TCP/UDP tunnels extensible); separate TCP/UDP port allocation is not supported for now.
* The DNS for domains using Tunnel upstreams should resolve to the designated TunnelRelay node.
* frp binaries (v0.61+) are packaged and provided by the system deployment script or container images.
## HTTPS Constraints
`proxy_routes.domain_cert_ids` is used to record the domain-certificate bindings parallel to `domains`; a value of `0` means the domain does not have HTTPS enabled and stays HTTP-only.
During rendering:
* Domains with certificates are grouped by certificate and output as independent `443 ssl` `server` blocks.
* Domains without certificates bound must not be automatically routed to HTTPS.
* All domains in `proxy_routes.domains` must be kept in the same site configuration to avoid being split across version snapshots.
## WAF Constraints
WAF centers around rule groups. The system provides a single global rule group (applied to all sites by default), on top of which websites can overlay multiple custom rule groups.
Core Capabilities:
* Supports individual IP / CIDR block whitelists and blacklists.
* Supports IP group references (including manual, automatic Expr calculated, and URL subscribed IP groups).
* Supports GeoIP-based country/region level admission filtering.
* Supports custom interception responses for rule groups (custom status codes and interception HTML pages, default is `418`).
IP Group & Judgment Constraints:
* **Runtime Decoupling**: The WAF runtime only reads local JSON files and does not access the Server database; configuration versions only store referenced IP group IDs. IP group members are synchronized via MD5 checksum differences and WebSocket push notifications, achieving hot activation without reloading Nginx.
* **Built-in Expr Rules**:
* High-frequency 404 scanning block: `request_count > 100 && status_404_ratio >= 0.8`
* Malicious IP direct probe: `ip_host_count > 50 && ip_host_ratio > 0.5`
* **Decision Priority**: The whitelist has absolute priority. If it does not match the whitelist, the blacklist funnel is triggered (global rule group first, custom groups matched in ascending ID order).
* GeoIP resolution depends on the local MaxMind database; if GeoIP is anomalous, region rules are automatically ignored and must not disrupt the availability of IP rules and the main reverse proxy chain.
## Authentication Source Constraints
`auth_sources` uniformly supports `github` and `oidc` login configurations. `external_accounts` stores bindings between third-party accounts and local users. Logic for first-time third-party login:
* If already bound, directly authorize login; if there is an active local session, automatically bind.
* If unbound and registration is enabled, automatically create a local account; if registration is closed, require the user to provide an existing local username and password to establish the association.
## Version & Observability Constraints
* `config_versions` must save the complete snapshot, rendering result, and `checksum`.
* Globally, only one version can be active at a time.
* Rollback is achieved by re-activating an older version.
* `nodes` only carry control plane state and low-frequency summaries; they do not carry high-frequency observability facts.
* Metrics, trends, and access analytics prioritize server-side aggregation rather than client-side temporary statistics.
* Access detail logs are only retained within a controlled time window, not evolving into a general logging platform.
## Documentation Maintenance Principles
* Update this document when the product range or system boundaries change.
* Update [System Architecture](./architecture.md) when the system structure or module responsibilities change.
* Update [Agent & Publish Model](./agent-design.md) when the publishing, synchronization, rollback, or Agent model changes.
* Update [Development Constraints](../../guideline/Constraints.md) when developer constraints, code specifications, or API conventions change.
* Update README and [Deployment Instructions](../../deployment/deployment.md) when deployment methods change.
* Update [Configurations Reference](../reference/configuration.md) when configuration items change.
* Completed phases should no longer be backfilled as "version plans".
* Before starting a new phase, complement the design first, then proceed to implementation.
* Product scope or system boundary changes: update this doc ([Product Boundaries](./index.md)).
* Log storage, log-table judgment, or switch-protocol changes: update [Log Store Decoupling](./logstore.md).
* System structure or component division changes: update [System Architecture](./architecture.md).
* Release, sync, rollback, or Agent model changes: update [Agent & Publish Model](./agent-design.md).
* Deployment method changes: update [Deployment Guide](../deployment/deployment.md) and the README.
* Config item changes: update [Configuration Reference](../reference/configuration.md).
+72 -76
View File
@@ -1,130 +1,126 @@
# Intranet Penetration Tunnel Design Document
# Tunnel & Intranet Penetration Design
You will learn: The architectural design of the OpenFlare intranet penetration tunnel, the internal principles of the dual-ended control components (Relay and Client), their interaction logics, and the communication flows for the data plane and control plane.
You will learn: the architecture design of OpenFlare's intranet penetration tunnels, the internal principles of the dual-end control components (Relay and Client), interaction logic, and the data-plane / control-plane communication flows.
---
## Requirements Analysis
In typical web application hosting scenarios, many origin servers (Origin Servers) are deployed in local intranet environments (such as local development machines, LAN servers, or firewalled private clusters). These servers typically suffer from:
1. **No Public IP**: Cannot be directly accessed by public internet traffic.
2. **Security Compliance Restrictions**: Creating port mappings (NAT) on border routers is strictly prohibited by security policies.
3. **Dynamic IP Changes**: Traditional DDNS solutions exhibit high latency and are highly unstable.
In typical web-hosting scenarios, many origins are deployed in intranet environments (local dev machines, LAN servers, or firewall-restricted intranet clusters). These servers usually:
1. **Have no public IP**: cannot be directly reached by public traffic.
2. **Compliance restrictions**: port mapping (NAT) on border routers is not freely allowed.
3. **Dynamic IP changes**: traditional DDNS is high-latency and unstable.
To allow internal origin servers to seamlessly integrate into the OpenFlare global data gateway, benefiting from premium features like WAF geographic protection and TLS certificate hosting, OpenFlare designed an end-to-end solution based on a **reverse relay penetration tunnel**. In this architecture, public edge nodes act as reverse proxy entrances and traffic relays, while the intranet side only needs to initiate secure outbound connections to achieve secure and stable reverse penetration of public traffic to internal origin servers.
To let intranet origins seamlessly join the OpenFlare global data gateway and enjoy value-added services like WAF geo protection and TLS certificate management, OpenFlare designs a **reverse-relay tunnel penetration** solution. In this architecture, public edge nodes act as the reverse-proxy entry and traffic relay; the intranet side only needs outbound secure connections to safely and stably reverse-penetrate public traffic to intranet origins.
---
## Core Capabilities
## Core Features
The intranet penetration tunnel subsystem includes the following core capabilities:
The intranet penetration subsystem includes:
* **Dynamic Relay Node Management**: The control plane dynamically dispatches relay services (frps), distributing service ports and authentication tokens dynamically.
* **Multi-Tunnel Reverse Proxy Mapping**: Supports mapping multiple internal web ports on a single intranet client, binding multiple domain routes to corresponding relay nodes.
* **Independent Process Lifecycle Control**: Both the relay and client are independent daemon processes written in Go, responsible for spawning, monitoring, self-healing, and hot-upgrading the underlying frp engine.
* **Token-based Independent Authentication**: The relay uses `agent_token` for authorization, whereas the intranet client uses its dedicated `tunnel_token`, enforcing isolation of permissions and routing boundaries.
* **Validation & Incremental Hot Reload**: Config files are rewritten and processes are gracefully reloaded only when tunnel bindings, certificates, or Relay topologies change, reducing runtime overhead.
* **Dynamic Relay node management**: the control plane dynamically dispatches the relay service (frps), dynamically distributing service ports and auth tokens.
* **Multi-tunnel reverse proxy mapping**: map multiple intranet web ports on a single intranet client, binding multi-domain routes to corresponding relay nodes.
* **Independent process lifecycle management**: both relay and client are standalone Go binaries that spawn, monitor, self-heal, and hot-upgrade the underlying frp engines.
* **Token-based auth isolation**: the relay uses `agent_token`; the intranet client uses its dedicated `tunnel_token` — permissions and route boundaries isolated.
* **Config validation and incremental hot reload**: config files are rewritten and processes reloaded only when tunnel bindings, certificates, or Relay topology actually change, reducing runtime overhead.
---
## Intranet Penetration & Tunnel Architecture
## Tunnel Architecture
The intranet penetration subsystem is integrated on top of the mature and high-performance `frp` tunnel protocol, divided into the **Control Plane** and the **Data Plane**.
The subsystem integrates the mature `frp` high-performance tunnel protocol, split into a **Control Plane** and a **Data Plane**.
```mermaid
graph TD
%% Data Flow
Browser[1. Browser / Visitor] -->|HTTPS Request| Agent[2. OpenResty / Agent]
Agent -->|Local proxy_pass| RelayFrps[3. OpenFlare Relay / frps]
RelayFrps -->|Encrypted Tunnel Protocol| FlaredFrpc[4. OpenFlared / frpc]
FlaredFrpc -->|Forward Local Request| LocalOrigin[5. Intranet Origin 192.168.x.x]
%% data flow
Browser[1. Browser / Visitor] -->|HTTPS request| Agent[2. OpenResty / Agent]
Agent -->|local forward proxy_pass| RelayFrps[3. OpenFlare Relay / frps]
RelayFrps -->|encrypted tunnel protocol| FlaredFrpc[4. OpenFlared / frpc]
FlaredFrpc -->|forward local request| LocalOrigin[5. Intranet origin 192.168.x.x]
%% Control Flow & Heartbeats
Server[OpenFlare Server Control Plane] <-->|Relay API / Heartbeat| RelayManager[openflare-relay process]
%% control flow & heartbeat
Server[OpenFlare Server control plane] <-->|Relay API / Heartbeat| RelayManager[openflare-relay process]
Server <-->|Client API / Heartbeat| ClientManager[openflared process]
RelayManager -.->|Control Process & Config| RelayFrps
ClientManager -.->|Control Multi-Relay Processes| FlaredFrpc
style Browser fill:#f9f,stroke:#333,stroke-width:2px
style LocalOrigin fill:#9f9,stroke:#333,stroke-width:2px
style Server fill:#f96,stroke:#333,stroke-width:2px
RelayManager -.->|manage process & config| RelayFrps
ClientManager -.->|manage multiple Relay processes| FlaredFrpc
```
* **Control Plane**: The Server maintains the database state. The `openflare-relay` process on relay nodes and the `openflared` process on intranet servers synchronize tunnel configurations via HTTP heartbeats and long-lived WebSocket connections.
* **Data Plane**: Public traffic enters the public edge Agent (OpenResty), where the TLS handshake, HTTPS termination, and WAF filtering are executed. It is then forwarded via `proxy_pass` to the co-located `openflare-relay (frps)` on the loopback address. `frps` encapsulates the HTTP requests into the encrypted TCP tunnel and sends them down to the intranet `openflared (frpc)`. Finally, `frpc` unpacks the requests and forwards them to the actual intranet origin service.
* **Control Plane**: the Server maintains DB state; `openflare-relay` on relay nodes and `openflared` on intranet servers sync tunnel config via HTTP heartbeats and WebSocket long channels.
* **Data Plane**: public traffic first enters the public-edge Agent (OpenResty), where HTTPS handshake, TLS termination, and WAF filtering happen; then `proxy_pass` forwards to the same-host `openflare-relay (frps)`. `frps` encapsulates the request and sends it through the persistent tunnel established with the intranet `openflared (frpc)`, which finally unpacks and dispatches to the actual intranet origin.
---
## Relay (Server-side) Design
## Relay Design
`openflare-relay` is a relay manager deployed on the public edge, running on nodes of type `tunnel_relay`.
`openflare-relay` is the relay manager deployed at the public edge, running on `tunnel_relay`-type nodes.
### 1. Core Architecture & Logic
* **Process Daemon**: The Relay process embeds the `frps` binary, spawning the `frps -c frps.toml` subprocess via `exec.Command` and using goroutines to asynchronously listen to its exit status. If `frps` exits unexpectedly, it automatically restarts using an exponential backoff policy.
* **Dynamic Configuration Rendering**: Periodically synchronizes status with the control plane via HTTP heartbeats to retrieve the active `RelayConfig`, including:
* `bindPort`: The public control port that frps listens to for incoming intranet frpc connections.
* `vhostHTTPPort`: The virtual host HTTP listening port where the Agent's proxy_pass points.
* `authToken`: The security credential used during the client connection handshake.
* `webServer`: Enables the frps dashboard API, which the Relay queries to collect active tunnel counts and traffic metrics.
* **Status Reporting**: In each heartbeat cycle, the Relay reports the active connections, registered clients, individual proxy tunnel statuses, and Relay version back to the Server.
* **Process guard**: the Relay process holds the `frps` binary, spawns `frps -c frps.toml` via `exec.Command`, and starts a goroutine asynchronously watching its exit state. If `frps` exits abnormally, it auto-restarts with backoff.
* **Dynamic config rendering**: syncs state to the control plane via HTTP heartbeat and fetches the current `RelayConfig`, mainly:
* `bindPort`: frps's public control port listening for intranet frpc client connections.
* `vhostHTTPPort`: vhost HTTP traffic port; the Agent's proxy_pass points here.
* `authToken`: security credential for client handshake validation.
* `webServer`: enables the frps dashboard API; the Relay collects real-time active tunnel counts and traffic metrics from this or the admin control port.
* **State reporting**: each heartbeat reports the underlying `frps` active connections, registered client count, per-proxy real-time state, and Relay version.
---
## Openflared (Client-side) Design
## Openflared (Client) Design
`openflared` is the client manager running inside the user's intranet server, authenticated using a dedicated `tunnel_token`.
`openflared` is the client manager on the user's intranet server, authenticated with its dedicated `tunnel_token`.
### 1. Core Design Mechanisms
* **Multi-Relay Support (Multiplexing)**:
To guarantee high availability and geographical proximity, the control plane may schedule the client to connect to multiple public Relays. `openflared` parses the list of Relays dispatched in the `TunnelConfig`, generating dedicated configurations (`frpc_<relay_node_id>.toml`) and allocating distinct cancelable contexts for each Relay process locally.
* **Independent Subprocess Monitoring**:
`openflared` maintains a local `processes` map to manage the lifecycles of individual `frpc` subprocesses. When the control plane adds or removes Relays, the client incrementally spawns new processes or gracefully shuts down obsolete ones without affecting other functioning tunnels.
* **Dynamic TOML Generation**:
When rendering TOML configs for each Relay, the client iterates over the Proxies list, writing each intranet service's `LocalAddr`, `LocalPort`, and bound `CustomDomains` into standard `[[proxies]]` blocks.
### 1. Core Mechanisms
* **Multiple Relay support (multiplexing)**:
for HA or nearest access, the control plane may schedule a client across multiple public Relays. `openflared` reads the Relays list in `TunnelConfig`, generates a dedicated config per Relay locally (named `frpc_<relay_node_id>.toml`), and assigns each Relay process an independent cancelable context.
* **Independent child-process monitoring**:
`openflared` maintains a `processes` map for per-`frpc` lifecycle management. When the control plane adds or removes a Relay, the client incrementally spawns new processes or gracefully shuts down old ones without affecting other working tunnels.
* **Dynamic TOML generation**:
when rendering the TOML for each Relay, the client iterates the Proxies list and writes each intranet service's `LocalAddr`, `LocalPort`, and bound `CustomDomains` into `[[proxies]]` blocks.
---
## Interaction Logic & Traffic Model
## Interaction Logic and Traffic Model
The intranet penetration subsystem implements consistent version control and status feedback loops.
The subsystem implements consistent versioning and state feedback.
### 1. Control Plane Publishing & Sync Flow
### 1. Control-Plane Release and Sync Flow
```text
Admin modifies tunnel/intranet port mappings -> Click Publish -> Generate new Tunnel version & Checksum
|
v (Push or Heartbeat Pull)
+-----------------------------------------------------------------------+-----------------------------------------------------------------------+
| |
v (Relay Side) v (Client Side)
openflare-relay heartbeat detects frps port/Token change openflared heartbeat detects tunnel_version change
Re-render local frps.toml Request full proxy configuration details
Kill and restart the frps process Re-render frpc_<relay_id>.toml configs
Report health status as healthy Restart or hot-reload changed frpc processes
Report application results (Apply Success/Error)
Admin modifies tunnel/intranet port mapping -> submit release -> generate new Tunnel version and Checksum
|
v (push or heartbeat pull)
+-------------------------------------------+-------------------------------------------+
| |
v (relay side) v (intranet client)
openflare-relay heartbeat detects frps port/Token changes openflared heartbeat detects tunnel_version change
re-render local frps.toml request latest proxy mapping package
kill and restart the frps process re-render frpc_<relay_id>.toml
report health state healthy restart changed Relay processes with hot reload
report apply result (Apply Success/Error)
```
1. **Versioned Controls**: All intranet tunnel routes and mapping relationships are version-controlled, dispatching a unique `version` and `checksum` to ensure clients do not repeatedly write files or trigger redundant reloads.
2. **Closed-Loop Application Feedback**: After applying new configurations, the client reports the application result in the next heartbeat. If the intranet port is unreachable or certificate bindings fail, the client intercepts the stdout/stderr of the subprocess to report `LastError` to the Server, providing administrators with transparent error details.
1. **Versioned control**: tunnel routes and mappings are versioned like the main routing system, dispatching `version` and `checksum` so clients don't rewrite or reload processes redundantly.
2. **Apply-result loop**: after applying new config, the client reports the result in its heartbeat. If frpc can't connect (intranet port unreachable or wrong cert config), the client captures process output and reports `LastError`, letting admins see penetration failure reasons directly in the Server.
### 2. Data Plane Traffic Model
1. **Public Entrance (Agent)**:
### 2. Data-Plane Traffic Model
1. **Public entry (Agent)**:
```nginx
server {
listen 443 ssl;
server_name intranet.example.com;
# ... TLS certificates & WAF filtering ...
# ... TLS cert & WAF filtering logic ...
location / {
proxy_pass http://127.0.0.1:18080; # Points to local frps vhost port
proxy_set_header Host $host; # Must preserve the original Host header, which frps relies on to route requests
proxy_pass http://127.0.0.1:8080; # points to the local frps vhost port
proxy_set_header Host $host; # must keep the original Host; frps routes by Host
proxy_set_header X-Real-IP $remote_addr;
}
}
```
2. **Relay Node (frps)**:
`frps` listens to the Vhost port `18080`. When an HTTP request arrives, it extracts `Host: intranet.example.com` from the request headers and searches its active registered tunnel registry to locate the matching encrypted TCP connection (initiated by the intranet frpc).
3. **Encrypted Tunnel Transmission (TCP)**:
`frps` encapsulates the HTTP request into the custom TCP tunnel protocol and transmits it down to the intranet `frpc` client.
4. **Intranet Client Distribution (frpc)**:
The `frpc` instance managed by `openflared` receives the payload, resolves it according to local settings (`localIP = "127.0.0.1"`, `localPort = 8080`), initiates a local TCP connection to forward the request to the intranet web service, and returns the response back through the tunnel to the public viewer.
2. **Relay node (frps)**:
`frps` receives the HTTP request on the vhost port (default `8080`), reads the `Host: intranet.example.com` header, and looks up the registered active-tunnel table for the matching encrypted TCP connection (established by the intranet frpc).
3. **Encrypted tunnel transport (TCP)**:
`frps` encapsulates the HTTP request into the internal TCP tunnel protocol and sends it to the intranet `frpc` client.
4. **Intranet client dispatch (frpc)**:
the `frpc` managed by `openflared` receives the packet, opens a local TCP connection per local config (`localIP = "127.0.0.1"`, `localPort = 8080`), forwards to the intranet web service, and returns the response along the same path to the public user.
+8 -125
View File
@@ -1,132 +1,15 @@
# WAF Design Document
# WAF Design
You will learn: The core architecture of the OpenFlare edge Web Application Firewall (WAF), the dynamic IP group asynchronous differential sync model, the high-performance OpenResty Lua caching scheme, and the complete request filtering and decision logic.
OpenFlare's current WAF rule model is a visual DAG. Node semantics, graph constraints, multi-rule ordering, release compilation, and migration boundaries are all governed by [WAF Orchestration Rule Design](./waf-orchestration-design.md).
---
## System Boundaries
## Requirements Analysis
The Server stores the edit graph with coordinates and a revision number; at release it re-validates and compiles it into a compact runtime graph; the Agent atomically writes the snapshot and reloads OpenResty; the request hot path only traverses the immutable in-memory graph in the Worker.
In public internet environments, web applications face a wide variety of security threats (such as scanner profiling, api scraping, malicious botnets targeted at specific regions, ransomware, and CC attacks). Allowing malicious requests to pass directly to the origin server (Origin Server) results in:
1. **Origin Server Overload**: High-frequency database queries and intensive CPU computations easily exhaust server resources.
2. **Sensitive API Abuse**: APIs like login, registration, and SMS verification codes can be maliciously exploited, leading to financial and computational losses.
3. **Data Exposure Risks**: Malicious common vulnerability probing actions are not intercepted proactively.
IP groups update independently of rule topology. Manual, subscription, and auto IP groups are maintained by the control plane; the Agent atomically replaces the JSON first and updates the checksum last. A coordinating worker checks the checksum every 5 seconds, reading and distributing the full snapshot only on change; on failure it keeps the previous valid data. The full runtime snapshot is capped at 20 MiB; Server release/sync and Agent disk writes use the same serialization validation; OpenResty uses a separate 64 MiB shared dict with non-evicting writes, refusing new versions on capacity shortage without breaking committed snapshots.
Therefore, OpenFlare needs to build a **high-performance, resiliently scalable WAF filtering engine** at the frontmost data plane layer (OpenResty). This engine is capable of executing deep filtering on malicious requests at the edge layer closest to users with sub-millisecond overhead. This relieves pressure on origin servers and provides core security capabilities like CC protection (PoW challenge), IP whitelisting/blacklisting, and region-level interception.
Geo nodes use Country and City MMDB. Docker images bundle the database files; bare-binary installs have the Agent download missing files at first startup and update them periodically per config; request handling always reads the DB already loaded by OpenResty. When the DB is unavailable, geo match returns `false` with a rate-limited warning; other execution errors must not be accidentally allowed through due to data corruption.
---
## Security Ordering
## Core Capabilities
OpenFlare WAF includes the following core protection dimensions:
* **IP Interception (IP Whitelist/Blacklist)**: Supports filtering by single IP or CIDR block, and aggregating tens of thousands of IPs into IP groups for highly efficient matching.
* **Geographical Whitelist/Blacklist (GeoIP Limit)**: Integrates MaxMind databases to support precise admission controls based on countries and provinces/regions.
* **Custom Interception Responses**: Supports custom block status codes (e.g., 403, 418) and personalized HTML block pages for different filtering rules.
* **Human-Machine Challenge (PoW CC Protection)**: Supports seamless client-side PoW challenges, calculating Hash collisions to prevent automated scripts and botnets from hitting endpoints concurrently.
---
## IP Group Design & Dynamic Asynchronous Sync
IP groups are the core containers for highly efficient IP whitelisting and blacklisting. OpenFlare classifies IP groups into three types based on their update frequencies and source channels:
### 1. IP Group Types
* **Manual**: Manually input by administrators in the control panel. Primarily used for static trusted IPs or long-term blocks.
* **Subscription**: Configured with remote text feeds (one IP/CIDR per line) or standard JSON subscription URLs. Server-side cron jobs periodically fetch and parse the remote subscription sources. Primarily used for integrating open-source threat intelligence feeds, cloud provider IP ranges, etc.
* **Automatic**: **The most resilient dynamic protection channel**. Control plane scanning jobs read access logs from all nodes, performing aggregation and analysis based on configured Expr rules (e.g., "requesting the `/api/login` endpoint over 50 times with a 401 status code in 5 minutes"). Once matched, the source IP is automatically added to a temporary block list for a specified duration.
### 2. Asynchronous Differential Sync Design (No Nginx Reload)
In traditional Nginx WAF designs, IP blacklist updates typically require writing configurations and executing reloads. If malicious IP blocks occur at high frequencies (seconds or minutes), frequent reloads force Nginx to constantly spawn new worker processes and tear down old ones, severely degrading performance.
OpenFlare adopts a **dynamic IP group asynchronous differential sync design**:
```text
WAF IP member updates (Manual/Subscription/Auto-trigger)
|
v
Server updates the database and calculates the new MD5 Checksum of the IP group
|
+----------------------------------------+
| (WebSocket Real-time Broadcast) | (Heartbeat Fallback Comparison)
v v
Server immediately pushes complete members Agent heartbeats report the local IP groups
of modified groups to all Agents checksum mapping table
| |
| v
| Server detects Checksum mismatch and dispatches
v the modified IP group members
Agent receives member data and writes it as JSON to local disk: waf_ip_groups.json
|
v (Lua Memory Awareness)
OpenResty Lua engine detects file changes via MD5 checksum in seconds and hot-updates its memory,
completely bypassing Nginx process reloads.
```
Through this architecture, the persistence and activation of tens of thousands of highly volatile dynamic blacklist IPs **require absolutely no Nginx reloads**, maximally protecting the high-concurrency throughput of the gateway.
---
## Rule Groups & Site Bindings
* **WAF Rule Group**: The smallest logical collection of WAF filtering policies. A single rule group can contain IP whitelists/blacklists, IP group references, regional restrictions, and CC protection configurations.
* **Global Rule Group**: When a rule group is marked as `is_global = true`, it takes effect on **all website routes** hosted on the node by default.
* **Site Binding**: Website routes (`proxy_routes`) can bind one or more non-global rule groups. During request validation, WAF evaluates the union of `Global Rule Group + Bound Rule Groups`.
---
## Implementation Details & High-Performance Caching
WAF is triggered in the OpenResty `access_by_lua` phase, implemented primarily through Lua files and local JSON configurations.
### 1. Physical Structures
* `waf_config.json`: Contains metadata for all rule groups, geographic country/region limits, and website-to-rule-group bindings.
* `waf_ip_groups.json`: Contains all synchronized IP groups and their corresponding IP lists.
* `waf/runtime.lua`: The actual runtime engine responsible for WAF rule comparison.
* `waf/check.lua`: The entry point for the access layer, handling packages inclusion and triggering `check()`.
### 2. Shared Memory Dictionary (ngx.shared) High-Performance Cache Design
Reading JSON files from the disk and decoding them upon every incoming web request would make disk I/O a severe performance bottleneck.
OpenFlare leverages the **OpenResty Shared Memory Dictionary (ngx.shared.openflare_waf_config)** to implement a two-level caching mechanism:
1. **Zero File I/O Path**:
In Lua, every time `check()` executes, it first computes the MD5 hash of the local JSON file using `ngx.md5` (which takes virtually zero time since the file is cached in the OS Page Cache).
2. **Hash Comparison & Hot Loading**:
It compares this against the cached hash key (`_config_hash`) stored in the shared memory dictionary.
* **If the hash is unchanged**: It reads the pre-decoded Lua Table configuration stored directly in shared memory. The entire verification runs purely in **shared memory**, completing in **microseconds**.
* **If the hash is mismatched**: Indicating that the Agent has just updated the WAF rules or IP groups on the disk, the Lua engine automatically reads the disk file, decodes it via `cjson.decode`, writes the decoded data and the new MD5 hash into shared memory, and makes it seamlessly readable by all subsequent worker processes.
---
## Application Flow & Decision Judgment Control Logic
When an HTTP/HTTPS request arrives at OpenResty, WAF evaluates and intercepts it step-by-step in the `access` phase according to the funnel decision chain below:
### 1. WAF Decision Flowchart
```mermaid
flowchart TD
A[Request enters access phase] --> B[Get Site Name of current request]
B --> C[Load all active rule groups bound to this Site in shared memory]
C --> D{Matches IP whitelist or Whitelist IP group?}
D -- Yes (Matched) --> E[Pass request - ALLOW]
D -- No --> F{Matches country/region whitelist?}
F -- Yes (Matched) --> E
F -- No --> G{Matches IP blacklist or Blacklist IP group?}
G -- Yes (Matched) --> H[Block request - BLOCK]
G -- No --> I{Matches country/region blacklist?}
I -- Yes (Matched) --> H
I -- No --> J{Is CC PoW verification enabled?}
J -- Yes --> K[Transfer to CC Protection module]
J -- No --> L[No security risks, pass normally]
H --> M[Exit and return custom status code and block page HTML configured in the rule group]
```
### 2. Decision Step Details
1. **Whitelist Precedence**:
To prevent false positives and guarantee smooth passage of core back-to-source traffic (such as search engine spiders, CDN back-to-source IPs, and office egresses), WAF **prioritizes matching IP whitelists and regional whitelists**. Once a whitelist matches, it immediately bypasses all subsequent blacklist checks and CC challenges.
2. **Blacklist Aggressive Block**:
If a request is not captured by the whitelist evaluation, it enters the blacklist funnel. Once the source IP matches an IP blacklist, a referenced blacklist IP group, or lies within a prohibited country/region, the Lua engine immediately marks `ngx.ctx.openflare_waf_blocked` as `true`.
3. **Response Output**:
Upon hitting the blacklist, Lua extracts the `block_status_code` (defaults to 418 or 403) and `block_response_body` (interception HTML page) configured in the matching rule group. It outputs the response body via `ngx.say()` and gracefully terminates the request using `ngx.exit(status)` to prevent the request from passing upstream.
Enabled global rules always run first; route rules execute by binding sequence. A block node terminates immediately; a pass node only ends the current rule; only after all rules pass does traffic enter the origin chain. Unknown nodes, missing outlets, or step-limit overruns always block the request.
+14 -12
View File
@@ -1,27 +1,29 @@
# Credits
OpenFlare is essentially a solution integration project. During its design and implementation phases, it drew inspiration from the exceptional concepts, architectural designs, and technical achievements of numerous open-source projects. Below are the key upstream open-source projects OpenFlare relies on for its core engine, security mechanisms, and backend/frontend system frameworks, along with our sincere thanks to these projects and their active communities.
OpenFlare draws on the excellent ideas, architectures, and technical implementations of many open-source projects during design and development. Below are the key open-source projects referenced in OpenFlare's core underlying engines, security mechanisms, and frontend/backend frameworks. We thank these projects and their communities.
---
### 1. OpenResty
* **Project Positioning**: A high-performance Web platform based on Nginx and Lua.
* **Role in OpenFlare**: Acts as the edge gateway for the global Data Plane. All public web traffic is received by OpenResty first, where high-concurrency HTTPS handshakes, WAF security rule evaluations, and PoW CC verification are performed before executing reverse proxies.
* **Project Link**: [OpenResty Official Website](https://openresty.org/)
* **Positioning**: a high-performance web platform based on Nginx and Lua.
* **Role in OpenFlare**: the edge gateway of the global data plane. All public web traffic is first received by OpenResty, where high-concurrency HTTPS handshakes, WAF security rule matching, anti-CC human verification, and finally reverse proxy forwarding are performed.
* **Link**: [OpenResty official site](https://openresty.org/)
### 2. FRP (Fast Reverse Proxy)
* **Project Positioning**: A high-performance reverse proxy application focused on intranet penetration.
* **Role in OpenFlare**: Serves as the underlying tunnel engine for the intranet penetration subsystem. The relay-side manager `openflare-relay` is responsible for running and scheduling the `frps` engine, while the intranet client `openflared` is responsible for generating TOML configurations locally and running the multiplexed `frpc` subprocesses.
* **Project Link**: [fatedier/frp (GitHub)](https://github.com/fatedier/frp)
* **Positioning**: a high-performance reverse proxy application focused on intranet penetration.
* **Role in OpenFlare**: the underlying tunnel engine of the intranet penetration subsystem. The relay-side manager `openflare-relay` guards and schedules the `frps` engine, while the intranet client `openflared` auto-generates TOML config locally and guards multiplexed `frpc` child processes.
* **Link**: [fatedier/frp (GitHub)](https://github.com/fatedier/frp)
---
### 3. Anubis (PoW Solution)
* **Project Positioning**: A lightweight human-machine verification and protection solution based on Proof of Work (PoW).
* **Role in OpenFlare**: Provides the core **seamless PoW CC challenge** capabilities for the gateway WAF.
### 3. Anubis (PoW solution)
* **Positioning**: a lightweight human-verification protection solution based on Proof of Work.
* **Role in OpenFlare**: provides the core **invisible anti-CC human challenge** capability for the gateway WAF.
---
### 4. gin-template
* **Project Positioning**: A modern full-stack development boilerplate based on Go Gin and frontend builds.
* **Role in OpenFlare**: Provided the standard, unified backend/frontend system architecture baseline for the OpenFlare control plane (Server).
* **Positioning**: a modern full-stack development scaffold template based on Go Gin and frontend builds.
* **Role in OpenFlare**: provides a canonical, unified frontend/backend system architecture prototype for the OpenFlare control plane (Server).
---
+53 -81
View File
@@ -1,100 +1,72 @@
# Publishing Your First Site
# Publish First Configuration
You will learn: How to create your first website configuration, bind origins and certificates, publish the configuration version, and verify that the Agent applied it successfully.
You will learn: how to create the first reverse proxy rule in the simplest way, publish a config version, and confirm the Agent has pulled and applied the config.
The publishing pipeline of OpenFlare centers on a complete configuration version snapshot. After modifying website configurations in the management console, you need to publish and activate the new version to let the Agent pull and apply it in the next heartbeat.
OpenFlare's release chain centers on "immutable config versions". After you modify rules in the admin panel, you must publish and activate a new version for online Agents to auto-sync and apply.
## Pre-publish Checks
---
Verify that the following conditions are met:
## Pre-Release Checks
| Item | Expectation |
Before starting, make sure the following conditions are met:
| Check | Required State |
| --- | --- |
| Server | Management console is accessible and log-in succeeds |
| Agent | At least one node is online |
| Origin | The Agent node can reach the origin server address |
| Domain | Domain is resolved to the OpenResty node, or prepared to verify via local `hosts` / `curl` Host header |
| HTTPS | If HTTPS is required, the certificate is uploaded or hosted |
| **Server** | control panel started normally and you can log in to the admin panel |
| **Agent** | at least one Agent node online (confirmable in「Node Management」) |
| **Origin** | your backend origin service is reachable from the Agent host |
| **Domain/testing** | the domain's DNS resolves, or you're ready to test with local hosts / curl Host header on the client |
## Create Website Configuration
---
A new website configuration requires at least:
## Step 1: Create the First Website Config
| Field | Description |
| --- | --- |
| Website Name | Business unique identifier; the primary domain is used if left blank |
| Domain | At least one domain, where the first is treated as the primary domain |
| Origin Address | A valid `http://` or `https://` upstream address |
| Enabled Status | Only enabled website configurations will participate in publishing and rendering |
For a quick verification, deploy a basic HTTP reverse proxy site first:
Example:
1. Log in to the control panel, go to **「Website Management」->「Domain List」** in the left navigation, click **「Add Zone」**.
2. Fill in the domain config:
* **Domain**: enter the test domain (e.g. `first.example.com`).
* **Bind Certificate**: choose not to bind a certificate (for HTTP quick verification).
* Click save to complete domain registration.
3. Go to **「Rule Management」**, click **「New Rule」**:
* **Rule Name**: enter a simple identifier (e.g. `first-app-route`).
* **Domain Match**: fill in your test domain (e.g. `first.example.com`).
* In the **「Reverse Proxy」** tab below, set the origin mode to「Direct Upstream」.
* **Upstream Address**: fill in the backend service address (e.g. the test-only `http://httpbin.org`).
* Click save to create the rule.
| Field | Example |
| --- | --- |
| Website Name | `app` |
| Domain | `app.example.com` |
| Origin Address | `http://10.0.0.20:8080` |
> [!TIP]
> **About HTTPS and certificate preparation**
> This section only guides the quick deployment of a basic HTTP rule. To import an existing SSL certificate or auto-issue one from Let's Encrypt via ACME and enable HTTPS proxying on port 443, go to [Create a Reverse Proxy Config](./proxy-config.md) for detailed steps.
A single domain can belong to only one website configuration. Rate limiting, reverse proxy, and caching parameters are shared site-wide.
---
## Bind Certificate
## Step 2: Preview and Publish a Config Version
HTTPS certificates are bound by domain. Domains without a bound certificate will not be placed into `443 ssl` server blocks automatically.
The new website config is still a draft in the Server database and needs a released version to be distributed to the data plane:
If a website contains multiple domains, the rendering pipeline groups the HTTPS configurations by certificate while ensuring all domains belong to the same site snapshot.
1. Click the **「Preview and Publish」** button in the top-right of the control panel; the system shows the physical config file diff for the newly added route.
2. After confirming the rendered config is correct, click **「Confirm Publish」**.
3. The control plane generates a unique config version number (format `YYYYMMDD-NNN`).
## Publish & Activate
---
Standard Pipeline:
## Step 3: Verify the Agent Applied It
```text
Modify rules -> Preview / Diff -> Publish -> Generate complete version -> Activate version -> Agent pulls -> Local application -> Report result
```
After publishing, the control plane immediately notifies online Agents via WebSocket (if the WebSocket is offline, the Agent detects it as a diff in its heartbeat):
During publication, the Server reads all enabled website configurations, the main OpenResty config templates, performance and cache parameters, rendering the complete OpenResty configuration and calculating its `checksum`, saving to `config_versions`, and switching the active version.
## Verify Results
Verify in the management console after publishing:
| Position | Expected Result |
| --- | --- |
| Node List | Node status is online |
| Node Details | Current version matches active version |
| Apply Logs | Most recent application succeeded |
| Version Page | The new version is currently active |
Verify Agent logs on the node:
```bash
journalctl -u openflare-agent -n 100 --no-pager
```
Access via domain:
```bash
curl -I http://app.example.com
```
If the domain has not been officially resolved, you can verify by specifying the Host header against the node IP:
```bash
curl -I -H 'Host: app.example.com' http://NODE_IP
```
HTTPS Validation:
```bash
curl -I https://app.example.com
```
## Rollback
If a target version application fails and triggers a rollback, the Agent blocks repeated synchronization of the same failing `version + checksum` until the active version or checksum changes on the control plane.
Roll back to an older version:
1. Open the Configuration Versions page.
2. Locate the last known good historic version.
3. Re-activate that version.
4. Check the node application logs to verify that the Agent successfully applied the rollback.
1. **Admin-side verification**: go to「Node Management」-> click the node to open details; check that the**current version number** has changed to the just-published latest active version and the「Apply Records」show success.
2. **Edge node verification**: check application via logs on the Agent host:
```bash
# If the Agent is Docker-deployed
docker logs openflare-agent
# If the Agent is deployed with local systemd
journalctl -u openflare-agent -n 50 --no-pager
```
3. **Connectivity test**:
On the client machine, use `curl` with a test Host header against the Agent node's IP for final verification:
```bash
curl -I -H "Host: first.example.com" http://AGENT_NODE_IP
```
If the returned status code matches the backend origin's response, your first reverse proxy rule has successfully landed on the edge node!
+34 -27
View File
@@ -1,43 +1,50 @@
# Guide Overview
# Guide
You will learn: How the OpenFlare documentation is organized, which pages to read when running it for the first time, and where to start for deployment, usage, troubleshooting, and development.
You will learn: how the OpenFlare docs are organized, which pages to read on first run, and where to start for deployment, usage, and troubleshooting.
OpenFlare is a self-hosted OpenResty control plane. It integrates reverse proxy website configurations, configuration version publishing, Agent node synchronization, TLS certificates, and basic observability into a single management console, making it ideal for a single team or organization managing multiple proxy nodes.
OpenFlare is a self-hosted OpenResty control plane. It brings reverse proxy website configs, config version release, Agent node sync, TLS certificates, and basic observability into one admin panel — suitable for a single team or organization managing multiple proxy nodes.
## Recommended Reading Path
If you are new to OpenFlare, read the documents in the following order:
If you're new to OpenFlare, read in this order:
1. [Quick Start](./quick-start.md): Start the Server using Docker Compose, log into the management console, and connect your first Agent.
2. [Basic Usage](./usage.md): Learn common operations for website configs, origins, certificates, publishing, rollbacks, and observability.
3. [Tunnel & Intranet Penetration](./tunnel-usage.md): Learn to deploy Relay and Client to achieve secure, public IP-free reverse penetration.
4. [WAF Security Protection](./waf-usage.md): Master IP whitelisting/blacklisting, WAF auto IP group aggregation Expr rules, geographical restrictions, and PoW CC protection.
5. [WAF Auto IP Group Expressions](./waf-ip-group-expr.md): Write auto IP group Expr rules and learn keyword definitions and presets.
6. [Deployment Guide](../deployment/deployment.md): Deploy Server and Agent in closer-to-production environments.
7. [Configurations Reference](../reference/configuration.md): Check Server environment variables, runtime Options, and Agent configurations.
8. [Troubleshooting](./troubleshooting.md): Troubleshoot login, database, node sync, OpenResty application, and frontend build issues.
1. [Quick Start](./quick-start.md): start the Server with Docker Compose, log in to the admin panel, and connect your first Agent.
2. [Publish First Configuration](./first-site.md): quickly create a basic HTTP reverse proxy site rule and verify the node applied it.
3. [Create a Reverse Proxy Config](./proxy-config.md): step by step, from certificate import and application to HTTPS, upstream origins, and edge cache.
4. [Zone Domain Migration](./zone-domain-migration.md): upgrade from legacy managed domains / inline route domains to the Zone model (automatic goose import), with backup, acceptance, and rollback notes.
5. [Pages Static Hosting Usage](./pages-usage.md): static project ZIP upload limits, SPA Fallback, and built-in API reverse proxy config.
6. [Tunnel & Intranet Penetration](./tunnel-usage.md): deploy Relay and Client for secure reverse penetration without a public IP.
7. [WAF Security Protection](./waf-usage.md): configure WAF rule groups; master IP allow/block lists, auto/subscription IP groups, geo restrictions, and PoW CC protection.
8. [WAF Auto IP Group Expressions](./waf-ip-group-expr.md): write auto IP group Expr rules; understand keyword meanings and preset rules.
9. [Uptime Kuma Monitoring Sync](./uptime-kuma.md): configure Uptime Kuma auto differential sync and monitor scope control.
10. [SSO Login Configuration](./sso.md): configure OIDC for third-party single sign-on (SSO).
11. [Troubleshooting](./troubleshooting.md): troubleshoot login, database, node sync, OpenResty, and edge cache hit issues by symptom.
12. [Credits](./credits.md): the excellent open-source projects and community acknowledgments this system depends on.
## Role-Based Entrypoints
## Find by Role
| What do you want to do? | Recommended Entrance |
| What you want to do | Recommended Entry |
| --- | --- |
| Run the console in under 5 minutes | [Quick Start](./quick-start.md) |
| Publish your first reverse proxy configuration | [Publish First Configuration](./first-site.md) |
| Get the admin panel running in 5 minutes | [Quick Start](./quick-start.md) |
| Publish your first reverse proxy config | [Publish First Configuration](./first-site.md) |
| Configure domain certs, reverse proxy, and edge cache | [Create a Reverse Proxy Config](./proxy-config.md) (incl. cache notes) |
| Static assets not hitting cache | [Troubleshooting · Edge Cache](./troubleshooting.md#edge-cache-hit-rate-anomalies) |
| Host an SPA or static website | [Pages Static Hosting Usage](./pages-usage.md) |
| Configure intranet penetration mapping | [Tunnel & Intranet Penetration](./tunnel-usage.md) |
| Configure CC protection & IP group blocking | [WAF Security Protection](./waf-usage.md) |
| Write auto IP group aggregation rules | [WAF Auto IP Group Expressions](./waf-ip-group-expr.md) |
| Configure anti-CC and IP group blocking | [WAF Security Protection](./waf-usage.md) |
| Write auto IP group rules | [WAF Auto IP Group Expressions](./waf-ip-group-expr.md) |
| Auto-sync monitored site status | [Uptime Kuma Monitoring Sync](./uptime-kuma.md) |
| Connect or reinstall a node Agent | [Access Agent](../deployment/agent.md) |
| Start Server from source code | [Launch Server](../deployment/server.md) |
| Configure GitHub or OIDC SSO | [SSO Login Configuration](./sso.md) |
| Start the Server from source | [Start the Server](../deployment/server.md) |
| Configure OIDC login | [SSO Login Configuration](./sso.md) |
| Upgrade Server or Agent | [Upgrade & Maintenance](../deployment/upgrade.md) |
| Participate in development or bug fixing | [Local Development](../design/development.md) and [Development Constraints](../../guideline/Constraints.md) |
| Understand architecture and publishing | [System Architecture](../design/architecture.md) and [Agent & Publish Model](../design/agent-design.md) |
| View open-source references and credits | [Credits](./credits.md) |
| Understand the architecture and release model | [System Architecture](../design/architecture.md) and [Agent & Publish Model](../design/agent-design.md) |
| See open-source references and acknowledgments | [Credits](./credits.md) |
## Documentation Partitions
## Doc Sections
`guide/` is oriented toward users and deployers, providing actionable steps from installation to daily operations.
`guide/` targets users and deployers with executable steps from install to daily operations.
`reference/` collects stable facts such as configuration fields, commands, API response structures, and repository layout.
`reference/` consolidates stable facts: config fields, commands, API response conventions, and repository structure.
`design/` is oriented toward maintainers and contributors, describing product boundaries, system architecture, Agent & publishing models, and engineering constraints. Before adding capabilities or changing boundaries, update the corresponding design document first.
`design/` targets maintainers and contributors, describing product boundaries, system architecture, the Agent & publish model, and engineering constraints. Before adding capabilities or changing boundaries, update the corresponding design doc first.
+124 -102
View File
@@ -1,69 +1,94 @@
# Quick Start
You will learn: How to start OpenFlare Server using Docker Compose, complete your first login, connect your first Agent, and verify if a configuration has been published to the node.
You will learn: how to start the OpenFlare Server with Docker Compose, complete the first login, connect your first Agent, and verify that a config has been published to a node.
The minimum running unit of OpenFlare consists of:
OpenFlare's minimal runtime consists of:
| Component | Responsibility |
| --- | --- |
| Server | Admin UI, Admin API, Agent API, configuration rendering, version publishing, and state storage. |
| Agent | Runs on the proxy node, pulls configurations, writes files for OpenResty, executes validations, and triggers reloads. |
| OpenResty | Receives actual traffic and reverse proxies it to origin servers. |
| Server | admin UI, admin API, Agent API, config rendering, version release, and state storage |
| Agent | runs on proxy nodes; pulls config, writes OpenResty, executes validation and reload |
| OpenResty | actually receives traffic and reverse proxies to origins |
The Agent manages the runtime through the OpenResty binary. A local deployment requires the `openresty` executable to be already present on the node; a Docker deployment can directly run the Agent image containing built-in OpenResty.
The Agent uniformly controls the runtime via the OpenResty binary. Local deployment requires an `openresty` executable on the node; Docker deployment can directly run the Agent image with a built-in OpenResty.
## Environment Requirements
| Item | Requirement |
| --- | --- |
| Docker / Docker Compose | Used to start Server and PostgreSQL; also used to run the Agent if using the Docker Agent image |
| OpenResty | Required to have the `openresty` executable when installing the Agent locally, or specify its path in the installation script |
| Reachable Ports | The Server listens on port `3000` by default; the Agent node needs to be able to reach the Server address |
| Browser | Used to access the management console |
| Docker / Docker Compose | starts the Server and its PostgreSQL, Valkey dependencies; also runs the Agent if using the Docker Agent |
| OpenResty | local Agent installs need an executable `openresty`, or specify the path in the install script |
| Reachable port | Server listens on `3000` by default; Agent nodes must be able to reach the Server address |
* **Docker**: `20.10.0+`
* **Docker Compose**: `2.0.0+`
---
## 1. Start the Server
Create a `docker-compose.yml` file in an empty directory:
Quick start recommends the standard **PostgreSQL + Valkey** deployment.
Create a `docker-compose.yaml` in an empty directory:
```yaml
version: '3.8'
services:
openflare:
image: ghcr.io/rain-kl/openflare:latest
container_name: openflare-server
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- openflare_uploads:/app/uploads
environment:
TZ: Asia/Shanghai
APP_SESSION_SECRET: 'replace-with-a-long-random-string' # replace with a long random string in production
DB_ENABLED: "true"
DB_HOST: "postgres"
DB_PORT: "5432"
DB_USERNAME: "${DB_USERNAME:-openflare}"
DB_PASSWORD: "${DB_PASSWORD:-replace-with-strong-password}"
DB_NAME: "${DB_NAME:-openflare}"
REDIS_ENABLED: "true"
REDIS_ADDR: "redis:6379"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
POSTGRES_DB: ${DB_NAME:-openflare}
POSTGRES_USER: ${DB_USERNAME:-openflare}
POSTGRES_PASSWORD: ${DB_PASSWORD:-replace-with-strong-password}
volumes:
- postgres-data:/var/lib/postgresql/data
- openflare_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-openflare} -d ${DB_NAME:-openflare}"]
interval: 10s
timeout: 5s
retries: 5
openflare:
image: ghcr.io/rain-kl/openflare:latest
redis:
image: valkey/valkey:8.0-alpine
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-a-long-random-string
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
command: ["valkey-server", "--appendonly", "yes"]
volumes:
- openflare-data:/data
- openflare_redis_data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres-data:
openflare-data:
openflare_uploads:
openflare_postgres_data:
openflare_redis_data:
```
Start the services:
@@ -72,46 +97,59 @@ Start the services:
docker compose up -d
```
Verify that the containers are running:
Confirm the containers are running:
```bash
docker compose ps
docker compose logs -f openflare
```
Once you see `server listening` in the logs and the `openflare` container status is running, access:
After seeing `server listening` and the `openflare-server` container status running, open in a browser:
```text
http://localhost:3000
```
Default credentials:
Default account:
| Username | Password |
| --- | --- |
| `root` | `123456` |
| `admin` | `12345678` |
Please change the default password immediately after your first login.
> [!WARNING]
> For your system's security, change the default password immediately after the first login.
## 2. Prepare Agent Token
If you forget the password and no password-recovery channel is configured, reset it with:
The Agent can be connected using one of two types of credentials:
```bash
go run main.go reset-passwd --user admin
```
| Credential | Applicable Scenario |
Without `--password`, the command auto-generates a random password and prints it to the terminal; you can also explicitly specify a new password with `--password`.
---
## 2. Prepare an Agent Token
Agents can connect with two credential types:
| Credential | Use Case |
| --- | --- |
| `discovery_token` | Automatically registers a node for the first time, which the Server exchanges for a node-specific Token |
| `agent_token` | Node has already been created/allocated in the management console, directly uses this node-specific Token |
| `discovery_token` | first-time auto registration; the Server exchanges it for a node-specific Token |
| `agent_token` | node already created or assigned in the admin panel; use the node-specific Token directly |
After preparing one of these credentials in the management console, proceed to the next step.
Prepare one of these credentials in the admin panel, then continue.
* **`discovery_token`** path: "System Settings" -> "Auto Registration"
* **`agent_token`** path: "Node Management" -> "Add Node"
- **`discovery_token`** menu path:「System Settings」->「OpenFlare」tab ->「Discovery Token & Deployment」→ Discovery Token
- **`agent_token`** menu path: after creating a node in「Node Management」, click into the node detail page to see its dedicated Token.
## 3. Install/Run the Agent
---
The recommended Agent deployment method is using Docker (which runs the Agent image with built-in OpenResty); deploying the Agent locally on the host using the installation script is also supported.
## 3. Install / Run the Agent
### Option A: Run Agent in Docker (Recommended)
Docker image deployment is recommended; you can also deploy to the local host via the install script.
### Option A: Run the Agent with Docker (recommended)
Run the Agent image directly on the proxy node:
@@ -119,18 +157,18 @@ Run the Agent image directly on the proxy node:
docker pull ghcr.io/rain-kl/openflare-agent:latest
docker rm -f openflare-agent 2>/dev/null || true
docker run -d --name openflare-agent --restart unless-stopped \
-p 80:80 -p 443:443 \
-v openflare-agent-data:/data \
-p 80:80 -p 443:443/tcp -p 443:443/udp \
-v openflare-agent-pages:/data/var/lib/openflare/pages \
-e OPENFLARE_SERVER_URL=http://your-server:3000 \
-e OPENFLARE_AGENT_TOKEN=YOUR_AGENT_TOKEN \
ghcr.io/rain-kl/openflare-agent:latest
```
### Option B: Execute Installation Script (Local Host Deployment)
### Option B: Run the install script (local deployment)
Execute the installation script on the proxy node.
Run the install script on the proxy node.
Using the `discovery_token`:
With `discovery_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
@@ -138,7 +176,7 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst
--discovery-token YOUR_DISCOVERY_TOKEN
```
Using the node-specific `agent_token`:
With the node-specific `agent_token`:
```bash
curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/install-agent.sh | bash -s -- \
@@ -146,74 +184,58 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/inst
--agent-token YOUR_AGENT_TOKEN
```
The script defaults to:
The script defaults:
| Item | Default Value |
| Item | Default |
| --- | --- |
| Install Directory | `/opt/openflare-agent` |
| Config File | `/opt/openflare-agent/agent.json` |
| systemd Service | `openflare-agent.service` |
| OpenResty Path | Automatically detects `openresty` if unspecified |
| Install directory | `/opt/openflare-agent` |
| Config file | `/opt/openflare-agent/agent.json` |
| systemd service | `openflare-agent.service` |
| OpenResty path | auto-finds `openresty` when unspecified |
Verify the Agent service status:
Confirm the Agent service state:
```bash
systemctl status openflare-agent
journalctl -u openflare-agent -f
```
If systemd is not available on the OS, the script outputs manual startup commands instead.
Without systemd, the script prints a manual start command.
## 4. Publish Your First Configuration
---
Perform the following operations in the management console:
## 4. Next Steps
1. Add a website configuration, filling in the website name, domain, and origin address.
2. Verify that the website configuration is enabled.
3. Check the preview or change summary before publishing.
4. Publish and activate the new version.
5. Wait for the Agent to detect and apply the version in the next heartbeat.
After starting the control panel and connecting an Agent node, you've successfully built the base runtime environment of the OpenFlare gateway. Continue with these two guides to deploy your first reverse proxy site:
The version number format is `YYYYMMDD-NNN`. Historic versions are immutable; rollbacks are accomplished by re-activating an older version.
1. **Publish your first website**:
* See [Publish First Configuration](./first-site.md). It guides you to publish your first proxy rule in the simplest way (plain HTTP) and verify the node applied it.
2. **Full reverse proxy config (HTTPS & origin management)**:
* See [Create a Reverse Proxy Config](./proxy-config.md). It guides you from certificate import/application to domain HTTPS certificate binding, origin management, and preview release.
## 5. Verify Success
---
Confirm in the management console:
## When You Hit Problems
| Position | Expected Result |
| --- | --- |
| Node List | Agent node status is online |
| Node Details | Current version matches active version |
| Apply Logs | Most recent application succeeded |
| Version Page | The new version is currently active |
Handle in this order:
Confirm on the Agent node:
1. Upgrade Server and Agent to the latest version; confirm whether the problem persists.
2. Re-publish and activate a config version, wait for the node to apply.
3. Run「Force Sync」on the target node in the node detail page to push an immediate config pull.
4. Rebuild or reinstall the Agent (re-run the install script).
5. If none of the above works, file a [GitHub Issue](https://github.com/Rain-kl/OpenFlare/issues) with the Server logs and node apply records.
```bash
journalctl -u openflare-agent -n 100 --no-pager
```
## Common Failures
| Symptom | Troubleshooting Direction |
| --- | --- |
| Management console fails to load in browser | Verify that the Server is running in `docker compose ps` and port `3000` is not bound by other processes |
| Data fails to save after logging in | Check the health of the PostgreSQL container, and verify the username, password, and database name in `DSN` |
| Agent fails to register | Verify that the Agent node can reach `--server-url`, and verify if the Token is typed correctly or expired |
| Agent is online but configuration is not applied | Verify that the website configuration is enabled and a version has been published and activated |
| OpenResty application fails | Review node application logs and `journalctl -u openflare-agent`, checking domains, certificates, upstreams, and port conflicts |
For more troubleshooting details, see [Troubleshooting](./troubleshooting.md).
More troubleshooting: [Troubleshooting](./troubleshooting.md).
---
## Advanced Deployment Guides
Once you complete the quick start and familiarize yourself with the basic operations of OpenFlare, you can read the following advanced deployment documents to put components into production:
After completing the quick start and getting familiar with OpenFlare, read these advanced deployment docs to put components into production:
* **Server Production Deployment**: Read [Launch Server](../deployment/server.md) to learn how to build the frontend from source, configure system environment variables, and run with Docker Compose.
* **Agent Production Integration**: Read [Deploy Agent](../deployment/agent.md) to learn about systemd-based service management, detailed local configuration parameters, and troubleshooting.
* **Tunnel Relay Deployment**: Read [Deploy Relay](../deployment/relay.md) to learn how to configure public relay nodes (frps) for penetration tunnels.
* **Tunnel Client Deployment**: Read [Deploy OpenFlared](../deployment/openflared.md) to learn how to run the penetration daemon client (frpc) on the intranet server side.
* **Production Deployment Topology**: Read [Deployment Guide](../deployment/deployment.md) to learn about high-availability production topologies and overall network planning.
* **System Upgrades & Maintenance**: Read [Upgrade & Maintenance](../deployment/upgrade.md) to learn how to upgrade the Server and individual node Agents smoothly.
* **Server production deployment**: read [Start the Server](../deployment/server.md) for building the frontend from source, system env vars, and Docker Compose.
* **Agent production access**: read [Access Agent](../deployment/agent.md) for systemd service management, detailed local config file fields, and troubleshooting.
* **Intranet relay deployment**: read [Deploy Relay](../deployment/relay.md) for configuring public relay nodes (frps) for tunnels.
* **Intranet client deployment**: read [Deploy OpenFlared](../deployment/openflared.md) for running the tunnel daemon client (frpc) on the intranet server.
* **Production topology reference**: read [Deployment Guide](../deployment/deployment.md) for production HA topology and overall network planning.
* **Upgrades and maintenance**: read [Upgrade & Maintenance](../deployment/upgrade.md) for smooth upgrades of the Server and Agent nodes.
+41 -62
View File
@@ -1,106 +1,85 @@
# SSO Login Configuration
You will learn: How to configure GitHub OAuth or standard OIDC login portals for OpenFlare, how to fill in callback URLs, and how third-party accounts bind to existing local users.
You will learn: how to configure an OIDC third-party login entry for OpenFlare, fill in the callback URL, and how third-party accounts bind to local users.
OpenFlare supports third-party logins configured via Authentication Sources. Currently, GitHub OAuth and standard OIDC Providers (e.g., Logto, authentik, Keycloak, Casdoor) are supported.
OpenFlare connects third-party login through OIDC auth sources. Any service providing standard OIDC Discovery (Google, Keycloak, authentik, Logto, Casdoor, etc.) can be integrated.
Once an Authentication Source is configured and enabled, it displays in the third-party login section of the login page. Users can log in using their third-party accounts or bind their third-party accounts to their current local account while logged in.
After an auth source is configured and enabled, it appears in the third-party account login area on the login page. Users can log in with a third-party account, or bind a third-party account to the current local account while logged in.
## Prerequisites
Before starting, prepare the following:
| Item | Description |
| --- | --- |
| OpenFlare URL | The actual URL accessed by user browsers, e.g., `https://openflare.example.com` |
| Auth Source Name | Unique internal identifier in OpenFlare, e.g., `github`, `company-oidc` |
| Client ID | Provided after creating an application in the third-party platform |
| Client Secret | Provided after creating an application in the third-party platform |
| OIDC Discovery URL | Required for OIDC only, e.g., `https://idp.example.com/.well-known/openid-configuration` |
| Server access URL | configured in admin「System Settings」->「System Settings」tab ->「General Settings」; must match the address users' browsers actually visit (protocol, domain, port) |
| Auth source name | unique identifier inside OpenFlare, e.g. `company-oidc` |
| Client ID | provided after creating the app on the third-party platform |
| Client Secret | provided after creating the app on the third-party platform |
| OIDC Discovery URL | e.g. `https://idp.example.com/.well-known/openid-configuration` |
**Verify that "System Settings -> General Settings -> Server Address" accurately matches your domain name.**
The Auth Source name can only contain letters, numbers, hyphens, or underscores, and must start with a letter or number. The Auth Source name will appear in the callback URL; if you modify the name after saving, you must simultaneously modify the callback URL on the third-party platform.
The auth source name may only contain letters, digits, hyphens, or underscores, and must start with a letter or digit.
## Callback URL
The Redirect URI / Callback URL in third-party platforms is formatted as:
The Redirect URI / Callback URL on the third-party platform is fixed to:
```text
<OpenFlare URL>/oauth/<Auth Source Name>
<server access URL>/login
```
Example:
For example, with a server access URL of `https://openflare.example.com`:
```text
https://openflare.example.com/oauth/github
https://openflare.example.com/oauth/company-oidc
https://openflare.example.com/login
```
When creating or editing an authentication source in the management console, the form automatically generates the callback URL based on your current browser URL and the Auth Source name you entered.
## Configure GitHub Login
1. Create an OAuth App in GitHub.
2. Fill `Homepage URL` with your OpenFlare URL.
3. Fill `Authorization callback URL` with the callback URL generated in OpenFlare, e.g., `https://openflare.example.com/oauth/github`.
4. Copy the Client ID and Client Secret provided by GitHub.
5. Log into the OpenFlare management console, go to "Settings -> System Settings -> Configure Authentication Sources".
6. Add an authentication source, choosing `GitHub` as the type.
7. Fill in the Auth Source name, display name, Client ID, and Client Secret.
8. The Scope defaults to `user:email`, which usually requires no modification.
9. Save and enable the authentication source.
Once enabled, the corresponding GitHub login button will display on the login page.
The callback URL only relates to the「server access URL」and does not include the auth source name. After the third-party platform completes authorization, it redirects here, and the OpenFlare login page uses the authorization code to complete login or binding.
## Configure OIDC Login
1. Create an application or client in your OIDC Provider.
2. Select Web / Confidential Client as the application type.
3. Fill `Redirect URI / Callback URL` with the callback URL generated in OpenFlare, e.g., `https://openflare.example.com/oauth/company-oidc`.
4. Copy the Client ID and Client Secret.
5. Retrieve the Provider's Discovery URL, which usually ends with `/.well-known/openid-configuration`.
6. Log into the OpenFlare management console, go to "Settings -> System Settings -> Configure Authentication Sources".
7. Add an authentication source, choosing `OIDC` as the type.
8. Fill in the Auth Source name, display name, Client ID, Client Secret, and OIDC Discovery URL.
9. Scope defaults to `openid profile email`. If the Provider restricts scopes, adjust to values permitted by the Provider.
10. Save and enable the authentication source.
1. Create an app or client on the OIDC Provider; choose Web / Confidential Client as the app type.
2. Set the Redirect URI / Callback URL to `<server access URL>/login`.
3. Copy the Client ID and Client Secret.
4. Get the Provider's Discovery URL, usually ending in `/.well-known/openid-configuration`.
5. Log in to the OpenFlare admin panel, go to **「System Settings」**, select the **「Security Settings」** tab, and add an auth source in the **「Auth Source Management」** section.
6. Choose type `OIDC`; fill in the auth source name, display name, Client ID, Client Secret, and OIDC Discovery URL.
7. Scope defaults to `openid profile email`. If the Provider restricts scopes, adjust according to the Provider's allowed values.
8. Save and enable the auth source.
Once enabled, the corresponding OIDC login button will display on the login page.
Once enabled, the login page shows the corresponding third-party login button.
## Login & Binding Behaviors
## Login and Binding Behavior
Once a third-party account returns to OpenFlare, it is processed according to the following rules:
When a third-party account returns to OpenFlare, it is handled as follows:
| Scenario | Behavior |
| --- | --- |
| Third-party account is already bound to a local user | Logs in directly |
| User is already logged in and initiates third-party authorization | Binds to the current local user |
| Third-party account is unbound, and registration is enabled | Automatically creates a standard user and binds |
| Third-party account is unbound, and registration is disabled | Prompts to enter an existing local username and password to complete the binding |
| Third-party account already bound to a local user | log in directly |
| User already logged in and initiates third-party authorization | bind to the current local user |
| Third-party account not bound, and registration allowed | auto-create a normal user and bind |
| Third-party account not bound, and registration disabled | require entering an existing local account password to bind |
If you want only existing users to use SSO, you can disable user registration. Unbound third-party accounts will then trigger the binding flow.
To allow only existing users to use SSO, turn off user registration. Unbound third-party accounts then enter the bind-existing-account flow.
## Modify Authentication Source
## Modifying an Auth Source
When editing an authentication source, leaving the Client Secret field blank retains the existing secret; entering a new value will overwrite the saved secret.
When editing an auth source, leaving the Client Secret input empty keeps the existing secret; entering a new value overwrites and saves it.
If you modify the Auth Source name, the callback URL changes accordingly. You must modify the Redirect URI / Callback URL on the third-party platform; otherwise, the third-party platform will deny the callback or return an error.
Changing the auth source name does not affect the callback URL, so the third-party platform config doesn't need to change.
## Common Problems
## FAQ
### Returns `invalid_scope`
This indicates that the third-party platform does not permit the configured Scope. OIDC defaults to `openid profile email`, and GitHub defaults to `user:email`. Adjust the Scope in the authentication source edit page or configure the third-party platform to permit the scope.
The third-party platform doesn't allow the currently configured scope. The OIDC default scope is `openid profile email`. Adjust the scope on the auth source edit page, or allow the scope on the third-party platform.
### Callback Address Mismatch
### Callback URL mismatch
Verify if the Redirect URI / Callback URL configured in the third-party platform matches the prompt in the OpenFlare form exactly. The protocol, domain, port, and path must match.
Check that the Redirect URI / Callback URL on the third-party platform exactly matches `<server access URL>/login`. Protocol, domain, port, and path must all match.
### Third-party Login Button Not Showing on Login Page
### Login page doesn't show the third-party login button
Verify if the authentication source is enabled and confirm that the Client ID and Client Secret are saved. OpenFlare validates these fields before enabling the source.
Check that the auth source is enabled and that the Client ID and Client Secret are saved. OpenFlare validates these fields before enabling the auth source.
### Client Secret Saved but Not Displayed in Clear Text
### Client Secret saved, but the list doesn't show the plaintext
This is expected behavior. OpenFlare does not echo the Client Secret back via API, displaying only whether the secret is configured.
This is expected. OpenFlare never echoes the Client Secret through the API; it only shows whether a secret is configured.
+110 -138
View File
@@ -1,45 +1,46 @@
# Troubleshooting
You will learn: How to troubleshoot OpenFlare Server, database, login, Agent, OpenResty, configuration publishing, and frontend build issues by symptoms.
You will learn: how to troubleshoot OpenFlare Server, database, login, Agent, OpenResty, and config release issues by symptom.
During troubleshooting, first identify which layer the issue occurs in: browser, Server, database, Agent, OpenResty, origin server, or DNS. OpenFlare configurations are not written directly to nodes online; only after the active version changes will the Agent detect and apply it in heartbeats.
First determine which layer the problem is in: browser, Server, database, Agent, OpenResty, origin, or DNS. OpenFlare configs are not written to all nodes online directly — only after the active version changes do Agents detect and apply it in their heartbeat.
## Quick Diagnostic
## Quick Locate
| Symptom | Where to check first |
| Symptom | Look Here First |
| --- | --- |
| Admin panel fails to open | Server container or process logs, port listening |
| Login anomalies | Default credentials, Session Secret, browser request payloads, Server logs |
| Data fails to save | Database connection, SQLite file permissions, PostgreSQL health |
| Agent offline | Agent logs, Token, Server URL, network connectivity |
| Node not updated after publishing | Active version, node heartbeat, application logs |
| OpenResty application failed | Application logs, Agent logs, certificates, upstream addresses, port conflicts |
| Observability analytics has no data | OpenResty container status, observability port, Agent retry logs |
| Admin panel won't open | Server container/process logs, port listening |
| Login abnormal | default account, Session Cookie, Server logs |
| Data won't save | DB connection, SQLite file permissions, PostgreSQL health |
| Agent offline | Agent logs, Token, Server address, network connectivity |
| Node not updated after release | active version, node heartbeat, apply records |
| OpenResty apply failure | apply records, Agent logs, certificates, upstream addresses, port usage |
| Access analytics empty | OpenResty container state, observability port, Agent backfill logs |
| Static assets never hit cache | global/site cache switch, policy extensions, whether config released, access log `cache_status`, origin Set-Cookie / Cache-Control |
## Server Fails to Start
## Server Won't Start
1. View logs:
1. Check the logs:
```bash
docker compose logs -n 200 openflare
```
For source-code execution, inspect terminal outputs.
For source runs, check terminal output.
2. Check port conflicts:
2. Check port usage:
```bash
lsof -i :3000
```
3. If using PostgreSQL, verify that the database is healthy:
3. If using PostgreSQL, confirm DB health:
```bash
docker compose ps postgres
docker compose logs -n 100 postgres
```
4. If using SQLite, verify that the database directory is writable:
4. If using SQLite, confirm the DB file directory is writable:
```bash
ls -ld "$(dirname /path/to/openflare.db)"
@@ -47,203 +48,174 @@ ls -ld "$(dirname /path/to/openflare.db)"
Common causes:
| Log or Symptom | Action |
| Log or Symptom | Handling |
| --- | --- |
| Database connection failed | Check `DSN` username, password, host, port, dbname, and `sslmode` |
| SQLite fails to create files | Check if the parent directory of `SQLITE_PATH` exists and is writable |
| Port is already in use | Change `PORT` or `--port`, or stop the process binding to the port |
| DB connection failed | check `DB_HOST`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD`, `DB_NAME`, `DB_SSL_MODE` consistency |
| SQLite can't create file | check the `SQLITE_PATH` directory exists and is writable |
| Port occupied | change `PORT` or `--port`, or stop the process holding the port |
## Admin Console Fails to Load or Shows Blank Page
## Admin Panel Won't Open or Is Blank
1. Verify that the Server is listening:
1. Confirm the Server is listening:
```bash
curl -I http://127.0.0.1:3000
```
2. If running from source, verify that the frontend static assets have been built:
2. Check that the browser access address matches the reverse proxy config.
## Default Account Can't Log In
The default account is `admin` / `12345678`. If the password was changed after first login, use the changed one.
Steps:
1. Confirm you're connected to the intended database — avoid `SQLITE_PATH` or `DB_HOST` / `DB_NAME` pointing at another environment.
2. Check whether the Server log uses `sqlite` or `postgres`.
3. In browser dev tools, confirm admin API requests carry the Session Cookie correctly.
4. Clear browser cache and cookies, then log in again.
### Emergency Admin Password Reset
If you forget the `admin` password, reset it with the `reset-passwd` command (supports SQLite and PostgreSQL):
```bash
cd openflare-server/web
pnpm build
go run main.go reset-passwd --user admin --password your-new-password
```
3. Verify if the browser URL matches your reverse proxy domain.
With SQLite, stop the Server process first to avoid DB file lock conflicts. Without `--password`, the command generates a random password and prints it. After resetting, log in and change the password immediately.
4. If accessing via the frontend dev server, verify the backend proxy configuration:
## Agent Can't Register or Stays Offline
```bash
cd openflare-server/web
NEXT_DEV_BACKEND_URL=http://127.0.0.1:3000 pnpm dev
```
## Default Credentials Fail to Log In
The default credentials are `root` / `123456`. If you have modified the password after your first login, use your new password.
Troubleshooting Steps:
1. Confirm that you are connecting to the expected database, avoiding `SQLITE_PATH` or `DSN` pointing to a different environment.
2. Check the Server log to see if it is running on `sqlite` or `postgres`.
3. If deployed in multi-replicas or behind a reverse proxy, verify that `SESSION_SECRET` is static and uniform across all instances.
4. Clear browser Cookies and try logging in again.
### Emergency Reset of Admin Password
If you forget the password for the `root` account, you can reset it back to `123456` by directly updating the password hash in the database (please change it immediately after logging in):
#### 1. If using SQLite Database
Stop the Server and open the database file using the `sqlite3` client:
```bash
sqlite3 /path/to/openflare.db
```
Execute the following SQL statement:
```sql
UPDATE users SET password_hash = '$2a$10$wN9aE3zTz83rO7R1uKlhuehJtA3c604pX4Z12B/9.5c0X337t1L4m' WHERE username = 'root';
```
Type `.exit` to exit and restart the Server.
#### 2. If using PostgreSQL Database
Connect to your PostgreSQL instance using a database tool (e.g., `psql`, `pgAdmin`, or `DBeaver`), select the corresponding `openflare` database, and execute the following SQL:
```sql
UPDATE users SET password_hash = '$2a$10$wN9aE3zTz83rO7R1uKlhuehJtA3c604pX4Z12B/9.5c0X337t1L4m' WHERE username = 'root';
```
Once executed successfully, you can log in using the default password `123456`.
## Agent Fails to Register or Stays Offline
Execute on the Agent node:
On the Agent node:
```bash
curl -I http://your-server:3000
```
Inspect Agent logs:
Check Agent logs:
```bash
journalctl -u openflare-agent -n 200 --no-pager
```
Verify configuration parameters:
Check the config file:
```bash
sed -n '1,160p' /opt/openflare-agent/agent.json
```
Key Settings:
Confirm:
| Configuration | Description |
| Config | Description |
| --- | --- |
| `server_url` | Must be the Server address reachable by the Agent node |
| `agent_token` / `discovery_token` | At least one must be provided |
| `heartbeat_interval` | Supports integer milliseconds or Go duration strings |
| `request_timeout` | Can be increased for slower network links |
| `server_url` | must be a Server address reachable by the Agent node |
| `agent_token` / `discovery_token` | at least one filled in |
| `heartbeat_interval` | supports millisecond integer or Go duration string |
| `request_timeout` | increase for slow networks |
If the log warns that the Token is invalid, retrieve a new Token in the management console, update `agent.json`, and restart the Agent:
If logs say the Token is invalid, prepare a new Token in the admin panel, update `agent.json`, then restart:
```bash
systemctl restart openflare-agent
```
## Node Fails to Apply New Version after Publishing
## Node Didn't Apply the New Version After Release
Verify in sequence:
Check in order:
1. Confirm that the target version is activated on the Versions page.
2. Verify if the node is online and if its last heartbeat time has updated.
3. Check the Application Logs for successful, warned, or failed logs for the target version.
4. Verify if the website configuration is enabled; disabled websites do not participate in rendering.
5. Inspect Agent logs for pulls, validations, reloads, or rollback events.
1. Is the target version activated in the version page?
2. Is the node online, and did the last heartbeat time update?
3. Do the apply records show success, warning, or failure for the target version?
4. Is the website config enabled? Disabled sites don't participate in release rendering.
5. Do Agent logs show pull, validation, reload, or rollback messages?
Inspect Agent logs:
View Agent logs:
```bash
journalctl -u openflare-agent -f
```
Note: If a target `version + checksum` fails to apply and triggers a rollback, the Agent blocks repeated synchronization of that failing target in its local state. You must fix the configuration issues and republish to generate a new checksum, or activate an older version to trigger a rollback.
Note: once a target `version + checksum` fails to apply and rolls back, the Agent blocks retrying that target in local state. After fixing the config, republish to generate a new checksum, or activate an older version to roll back.
If this is the Agent's first time applying configurations and no historic `nginx.conf` exists locally to roll back to, the failed version remains blocked but the Agent will attempt to enter the safe fallback runtime. At this point, the application logs and Agent logs will contain `fallback runtime started`. OpenResty will only listen to port `80`, returning a `503` with the body `OpenFlare: No Valid Configuration`, while retaining the local `/openflare/stub_status` health probe. After correcting the configurations and republishing, the Agent overrides the fallback config and restores normal reverse proxies.
If this is the Agent's first config apply with no historical `nginx.conf` to roll back to, the failed target is still blocked, but the Agent enters a safe fallback runtime. The apply records and Agent logs will contain `fallback runtime started`; OpenResty only listens on port `80` and returns `503` with `OpenFlare: No Valid Configuration` for everything, while keeping the local `stub_status` health endpoint. After fixing the config and republishing a new version, the Agent overwrites the fallback config and resumes normal proxying.
## OpenResty Application Fails
## OpenResty Apply Failure
Common Causes:
Common causes:
| Cause | Diagnostic |
| Cause | Troubleshooting |
| --- | --- |
| Domain or server block conflict | Verify if the same domain is used by multiple website configurations |
| Invalid upstream address | Confirm that all upstreams are valid `http://` or `https://` URLs |
| Mismatched multi-upstream format | Multi-upstreams must be pure `scheme://host[:port]` |
| Missing cert or invalid paths | Verify if domains are bound to certs and check if the Agent cert directory is writable |
| Port already in use | Verify ports `80` and `443` on the host |
| Domain or server block conflict | check whether the same domain is used by multiple site configs |
| Invalid upstream address | confirm all upstreams are `http://` or `https://` |
| Multi-upstream format violates constraints | multi-upstream must be plain `scheme://host[:port]` |
| Certificate missing or wrong path | check whether the domain is bound to a cert and the Agent cert dir is writable |
| Port occupied | check local `80`, `443` ports |
OpenResty Configuration Validation:
OpenResty config validation:
```bash
openresty -t -c /path/to/openflare/data/etc/nginx/nginx.conf
```
OpenResty Runtime Status:
OpenResty running state:
```bash
ps aux | grep openresty
```
The Agent determines OpenResty survival periodically using the local endpoint `http://127.0.0.1:<openresty_observability_port>/openflare/stub_status`, completely bypassing repeated `openresty -t` calls. If a node is marked as unhealthy, confirm if this local observability port is listening. If failures only occur when applying configurations (e.g., `host not found in upstream`), the failure lies in config validation or reload, not the periodic health checks.
The Agent's periodic health check probes the local `http://127.0.0.1:<openresty_observability_port>/openflare/stub_status` to judge OpenResty liveness — it does not repeatedly run `openresty -t`. If a node is marked unhealthy, first confirm that local observability port is listening; if `host not found in upstream` appears only during config apply, the failure comes from config validation or reload, not the periodic health probe.
Actual binary paths and main configuration paths are governed by `openresty_path` and `main_config_path` in `agent.json`.
Actual binary and main config paths follow `openresty_path` and `main_config_path` in `agent.json`.
## HTTPS Fails to Work
## HTTPS Not Taking Effect
1. Verify that the certificate has been uploaded or hosted.
2. Verify that the website configuration binds the certificate to the domain.
3. Confirm that the configuration version has been published and activated.
4. Check if the Application Logs indicate a success.
5. Check the certificate chain and status code using `curl`:
1. Confirm the certificate is uploaded or managed.
2. Confirm the site config's domain is bound to a certificate.
3. Confirm a new version was published and activated.
4. Check whether the apply records succeeded.
5. Inspect the certificate and status code with `curl`:
```bash
curl -Iv https://your-domain
```
Domains without a bound certificate will not be added to the HTTPS configuration automatically; this is expected behavior.
Domains without a bound certificate are not auto-added to the HTTPS config — that's expected.
## Traffic Analytics Has No Data
## Access Analytics Empty
1. Confirm that the node has successfully applied configurations carrying observability Lua scripts.
2. Verify that OpenResty is running.
3. Check Agent logs for observability extraction or upload errors.
4. Check if `openresty_observability_port` (default is `18081`) is bound by other processes.
5. Verify if the Server database has purged data inside the time window.
1. Confirm the node successfully applied config including the observability Lua assets.
2. Confirm OpenResty is running.
3. Check Agent logs for observability collection or backfill failures.
4. Check whether `openresty_observability_port` is occupied (default `18081`).
5. Confirm the Server's DB cleanup policy hasn't deleted the relevant time window.
## Frontend Build Fails
## Edge Cache Hit-Rate Anomalies
Execute:
Access log cache three states: **hit** (HIT/STALE/REVALIDATED/UPDATING), **origin** (MISS/EXPIRED), **not cached** (BYPASS or empty — request didn't enter a cacheable path or response wasn't stored). Design: [Edge Cache Strategy Design](../design/edge-cache-design.md).
```bash
cd openflare-server/web
corepack enable
pnpm install
pnpm lint
pnpm typecheck
pnpm test
pnpm build
```
### Checklist
Common causes:
1. Global OpenResty cache enabled in **Performance Settings**.
2. Site **Cache** enabled and policy matches the path (「Standard static assets」covers only built-in extensions, **not HTML/JSON**; `.js.map`'s extension is `map`, in the default table).
3. Config version **published and activated**, node apply records succeeded (changing cache rules without publishing leaves nodes on old bypass logic).
4. Request method is **GET** (non-GET is never cached).
5. Origin doesn't return **`Set-Cookie`** for the target URL (if so, not written to the edge).
6. Origin doesn't declare **`Cache-Control: private` / `no-store`** (shared caches won't store).
7. Browser DevTools "Disable cache" only affects the browser; whether the edge HITs is judged by access log `cache_status`, not the Network panel.
| Symptom | Action |
### Common Misconceptions
| Symptom | Explanation |
| --- | --- |
| pnpm version mismatch | Reinstall packages after executing `corepack enable` |
| TypeScript errors | Locate detailed file bugs by running `pnpm typecheck` |
| API type mismatch | Check responses structures in `lib/api/` and `types/` |
| E2E test failures | Confirm that both the Server and frontend dev server are running |
| Everything「not cached」after login, never republished | old config bypassed session cookies; after upgrade you must republish node configs |
| `/api/foo` or `/index.html` not cached under `static` | expected (extension not in the default cacheable table) |
| HTML cross-user leakage after switching to `all` | origin didn't forbid shared caching; switch back to `static` or add `private`/`no-store` to dynamic responses |
| URLs with `?v=` have low hit rates | default cache key includes the full `$request_uri`; different query = different object |
| First MISS, second still MISS | check whether the origin sets `Set-Cookie`/`private` every time, or node disk/cache `inactive` is too short |
## Documentation Build Fails
### Expected Behavior (aligned with Cloudflare defaults)
```bash
cd docs
pnpm install
pnpm build
```
If it fails on broken links, check if new pages are added to the `docs/config.ts` sidebar, or if relative markdown links point to existing markdown files.
* A logged-in user accessing `/_app/**/*.js` static assets: **can HIT**.
* Response with `Set-Cookie` or `private`: **not stored**.
* Cacheable status codes without origin cache headers: use the default Edge TTL (e.g. ~120 min for 200).
+89 -91
View File
@@ -1,174 +1,172 @@
# Tunnel & Intranet Penetration
You will learn: The design principles of OpenFlare intranet penetration tunnels, core concepts (Relay nodes and Tunnel clients), and how to safely and stably publish your intranet development environment or private cloud services to a public domain name from scratch.
You will learn: the design principles of OpenFlare's intranet penetration tunnels, core concepts (relay nodes and tunnel clients), and how to publish an intranet dev environment or private cloud service to a public domain step by step, securely and stably.
In many practical development and operations scenarios, our origin servers are deployed in local LANs, local development machines, or heavily guarded private VPCs, having no public IP address and no port mapping (NAT) configured on border firewalls or routers.
In many real development and ops scenarios, origin services run inside a LAN, on a local dev machine, or in a private VPC — with no public IP and no way to configure port mapping on the border firewall or router.
OpenFlare provides an end-to-end solution **based on reverse relay penetration tunnels**. You only need to initiate a secure outbound connection from your intranet environment to the public relay node, without configuring any inbound ports, to smoothly route public web traffic into your intranet origin. At the same time, you benefit from automatic TLS certificate hosting and WAF security protection provided by the gateway.
OpenFlare provides a complete **reverse-relay tunnel penetration** solution. You only initiate an outbound secure connection from the intranet to a public relay node — no inbound ports need to be configured — and public web traffic is routed into the intranet origin, while enjoying the gateway's automatic TLS certificate management and WAF protection.
---
## Core Concepts
Before using the intranet penetration features, you need to familiarize yourself with the following components and core concepts:
Before using intranet penetration, get familiar with these components:
| Concept | Description | Component / Operation |
| Component | Description | Corresponding Entity |
| --- | --- | --- |
| **Relay Node (Relay)** | Traffic relay services deployed at the public edge, responsible for listening to intranet client persistent connections, acting as the transit bridge between the gateway Agent (OpenResty) and internal traffic. | Node of type `tunnel_relay` running the `openflare-relay` daemon |
| **Penetration Tunnel (Tunnel)** | Logical penetration client instances having a globally unique ID and secure authentication token, used to identify a specific intranet environment. | Globally unique ID generated by Server `tunnel_id` (format: `tun-<32hex>`) |
| **Tunnel Client (Client)** | A lightweight controller running in the intranet environment, automatically managing the underlying frpc tunnel subprocesses according to the configuration dispatched by the Server. | The `openflared` container or independent binary process deployed in the intranet |
| **Tunnel Upstream (Tunnel Upstream)** | A special upstream type in the website configuration. When this type is selected, the gateway forwards public traffic to the Vhost port of the local relay node, eventually reaching the intranet origin. | Upstream of type `tunnel` configured in the website details |
| **Relay node** | a traffic relay service deployed at the public edge; listens for the intranet client's long connections and bridges gateway Agent (OpenResty) and intranet traffic | `tunnel_relay` node guarded by `openflare-relay` |
| **Tunnel** | a logical penetration client instance with a globally unique ID and an auth token, identifying one concrete intranet environment | `tunnel_client` node created in「Node Management」, assigned a dedicated Tunnel Token |
| **Tunnel client** | a lightweight controller running in the intranet; auto-manages the underlying frpc tunnel child processes based on Server-dispatched config | `openflared` container or standalone binary deployed in the intranet |
| **Tunnel upstream** | a special reverse proxy type in route rules. With this type, the gateway forwards public traffic to the local relay's Vhost port, eventually reaching the intranet origin | reverse proxy type configured in the「Rule Management」detail page, origin mode「Intranet Tunnel」with a bound Tunnel node |
---
## Recommended Operation Sequence
## Recommended Order
To publish an intranet service to the public internet, we recommend doing so in the following order:
To publish an intranet service to the public, follow this order:
1. Register and deploy at least one public **Relay Node (Relay)** and keep it online.
2. Create a **Penetration Tunnel (Tunnel)** in the management console and copy its dedicated Token.
3. Deploy and start the **Tunnel Client (OpenFlared)** on your intranet server.
4. Confirm that the status of the tunnel in the management console shows as "Online".
5. Add a website configuration, selecting **Intranet Penetration** as the upstream type, binding it to the corresponding tunnel, and entering the intranet port (e.g., `127.0.0.1:8080`).
1. Register and deploy at least one public **Relay node** and keep it online.
2. Go to **「Node Management」**, create a node of type **Tunnel node (tunnel_client)**, and get the dedicated Token.
3. Deploy and start the **tunnel client (OpenFlared)** on the intranet server.
4. Confirm the Tunnel node's status shows「Online」in the admin panel.
5. Add or edit a rule in **「Rule Management」**; in the「Reverse Proxy」tab choose origin mode **「Intranet Tunnel」**, bind the Tunnel node, and enter the intranet service port (e.g. `127.0.0.1:8080`).
6. Publish and activate the new version.
7. Access via the public domain to verify that the intranet penetration link is established.
7. Access via the public domain to verify the tunnel link.
---
## Detailed Configuration Steps
## Detailed Steps
### Step 1: Prepare the Relay Node (Relay)
### Step 1: Prepare a Relay Node
Intranet traffic is routed through public relay nodes. Before starting, ensure you have a public relay server available.
Intranet traffic needs a public relay node to transit. Before starting, make sure you have a usable relay server on the public network.
1. Log into the management console and go to **"Node Management"**.
2. Add a new node, selecting **Relay Node (tunnel_relay)** as the **Node Type**.
3. Save and copy the node-specific `agent_token`.
4. Start the `openflare-relay` process on your public server. You can run it quickly using Docker:
1. Log in to the admin panel, go to **「Node Management」**.
2. Add a new node and set **Node Type** to **Relay node (tunnel_relay)**.
3. After saving, copy the node's dedicated `agent_token`.
4. Start `openflare-relay` on your public server. Docker quick run:
```bash
docker run -d --name openflare-relay --restart unless-stopped \
-p 7000:7000 \
-e OPENFLARE_SERVER_URL=http://<YOUR_SERVER_PUBLIC_IP>:3000 \
-e OPENFLARE_AGENT_TOKEN=<YOUR_COPIED_AGENT_TOKEN> \
-e OPENFLARE_SERVER_URL=http://<your-Server-public-IP>:3000 \
-e OPENFLARE_AGENT_TOKEN=<the-AgentToken-you-copied> \
-v openflare-relay-data:/var/lib/openflare-relay \
ghcr.io/rain-kl/openflare-relay:latest
```
> [!IMPORTANT]
> Make sure to allow port `7000` (the control port for frpc client connections) in your cloud provider's security group. If your Server and Relay are deployed on the same machine, `OPENFLARE_SERVER_URL` should point to the Server's public or internal IP.
> Open port `7000` (the frpc client connection control port, default `relay_bind_port`) in the cloud server's security group. If your Server and relay node are on the same machine, `OPENFLARE_SERVER_URL` here should point to the Server's public or intranet communication IP.
### Step 2: Create a Penetration Tunnel in the Management Console
### Step 2: Create a Tunnel Node in the Admin Panel
1. Navigate to the **"Intranet Penetration"** section in the side navigation bar.
2. Click the **"Create Tunnel"** button and enter:
* **Tunnel Name**: Describes the intranet environment, e.g., `home-lab` or `office-dev`.
* **Description**: Optional, describes the purpose of this tunnel.
3. Click save, and the system will automatically generate a globally unique ID and a dedicated `tunnel_token` (e.g., `tun-xxxx...`).
4. Copy the **Client Deployment Command** generated in the popup window, which will be used in the next step.
1. Navigate to **「Node Management」** in the admin sidebar.
2. Click **「Add Node」**; in the dialog choose node type **「Tunnel node (tunnel_client)」**.
3. Fill in the node name and description, click save.
4. Click into the Tunnel node's detail page; find the dedicated **Tunnel Token** and the one-click client deployment command.
### Step 3: Deploy the Intranet Client (OpenFlared)
Return to your intranet server and execute the copied deployment command to run the client.
Back on your intranet server, run the client with the copied deployment command.
#### Option A: Deploy with Docker (Highly Recommended)
#### Option A: Deploy with Docker (recommended)
The official `openflared` image embeds the master daemon and `frpc` runtime, working out-of-the-box with no extra dependencies:
The official `openflared` image bundles the supervisor daemon and the `frpc` runtime — out of the box, no extra dependencies:
```bash
docker run -d --name openflared --restart unless-stopped \
-e OPENFLARE_SERVER_URL=http://<YOUR_SERVER_PUBLIC_IP>:3000 \
-e OPENFLARE_TUNNEL_TOKEN=<YOUR_COPIED_TUNNEL_TOKEN> \
-e OPENFLARE_SERVER_URL=http://<your-Server-public-IP>:3000 \
-e OPENFLARE_TUNNEL_TOKEN=<the-TunnelToken-you-copied> \
-v openflared-data:/app/data \
ghcr.io/rain-kl/openflared:latest
```
#### Option B: Host Binary Manual Execution
#### Option B: Run the host binary manually
If you cannot use Docker, you can download or compile the `flared` binary:
If Docker isn't convenient, download or build the `flared` binary yourself:
1. Create a `flared.json` configuration file in the same directory as the executable on your intranet machine:
1. Create a `flared.json` config file next to the program on the intranet machine:
```json
{
"server_url": "http://<YOUR_SERVER_PUBLIC_IP>:3000",
"tunnel_token": "<YOUR_COPIED_TUNNEL_TOKEN>",
"server_url": "http://<your-Server-public-IP>:3000",
"tunnel_token": "<the-TunnelToken-you-copied>",
"frpc_path": "/usr/local/bin/frpc",
"data_dir": "./data"
}
```
2. Execute the startup command:
2. Start it:
```bash
./flared -config ./flared.json
```
#### Verify Online Status
#### Status Confirmation
Once started successfully, the intranet client will send heartbeats through outbound networks to synchronize configurations. At this point:
1. Refresh the **"Intranet Penetration"** list in the management console; the tunnel status indicator should turn green and show **"Online"**.
2. Click tunnel details to view which public Relays the intranet client is currently connected to.
After starting, the intranet client sends heartbeat syncs to the control plane over outbound connections:
1. Refresh the **「Node Management」** list; the Tunnel node's status light should turn green **「Online」**.
2. Click into the node detail page to see which public Relay nodes the intranet client is connected to.
### Step 4: Create a Website and Bind the Tunnel Upstream
### Step 4: Configure the Route and Bind the Tunnel Upstream
Now you can configure public reverse proxy and domain routing for your intranet service.
Now configure public reverse proxying and domain access for your intranet service:
1. Go to the **"Website Configuration"** page and click **"Create Website"**.
2. Enter the **Domain Name** required to access the service publicly, e.g., `nas.example.com`.
3. Critical Configuration: In the **"Upstream Configuration"** section, switch the **Upstream Type** from "Direct" to **"Intranet Penetration"**.
4. In the dropdown list, select your newly deployed **Intranet Tunnel** (e.g., `home-lab`).
5. Enter the **Intranet Target Address** (the local address and port reachable by the intranet client, e.g., `127.0.0.1:8080`) and select the **Intranet Protocol** (usually `http`).
6. Configure other standard website settings (such as TLS certificates) and click save.
1. First go to **「Website Management」->「Domain List」** and register the domain you want to expose.
2. Go to **「Rule Management」**, click **「New Rule」** or edit an existing rule.
3. In the **「Reverse Proxy」** tab, switch the **origin mode** to **「Intranet Tunnel」**.
4. Select the online **Tunnel node** from the dropdown.
5. Fill in the **intranet target address** (a local address/port reachable by the intranet client, e.g. `127.0.0.1:8080`) and **intranet protocol** (usually `http`).
6. Configure other regular site options and save.
### Step 5: Publish & Activate
### Step 5: Publish and Apply
To allow the gateway's OpenResty instance to match and route domain traffic correctly, we need to publish a new configuration version.
To let the gateway's OpenResty match and route domain traffic, publish a new config version:
1. Click **"Preview Config"** in the top right corner of the navigation bar to verify the generated configurations.
2. In the popup window, click **"Publish & Activate"**.
3. Now, the public edge Agent pulls the latest routing, forwarding requests for `nas.example.com` to the loopback virtual host port of `openflare-relay (frps)`.
4. The intranet client `openflared (frpc)` receives the relayed packets, securely hands them over to the local `127.0.0.1:8080` service, and returns responses back through the tunnel.
5. Access `nas.example.com` in your browser to confirm that the intranet service displays successfully!
1. Click **「Preview and Publish」** in the top-right nav; confirm the generated site config is correct.
2. In the dialog, click **「Confirm Publish」**.
3. The public-edge Agent now pulls the latest route: it forwards requests to the same-host `openflare-relay (frps)` vhost port.
4. The intranet client `openflared (frpc)` receives the relayed packets and safely forwards them to the intranet `127.0.0.1:8080` service, returning the response along the same path.
5. Visit the domain in a public browser to confirm the intranet service displays.
---
## Advanced Application Scenarios
## Advanced Scenarios
### 1. Single-Tunnel Multi-Service Multiplexing (Multi-Port Mapping)
### 1. One Tunnel, Multiple Services (multi-port mapping)
You do not need to deploy an `openflared` container for every single internal service.
You don't need a separate `openflared` container for every intranet service.
If you want to map multiple different services in the same intranet environment (e.g., `127.0.0.1:80` for a blog, `127.0.0.1:8080` for an API, and `192.168.1.120:9000` for a local network drive):
1. Keep this single `openflared` client online.
2. Create three independent website configurations in the management console (binding their respective public domains).
3. Set the **Upstream Type** to **the same intranet tunnel** for all three website configurations.
4. Fill in their respective "Intranet Target Addresses" (e.g., `127.0.0.1:80`, `127.0.0.1:8080`, and `192.168.1.120:9000`).
5. Publish and activate the new version to achieve single-tunnel multi-service multiplexing.
To map multiple services in one intranet environment (e.g. `127.0.0.1:80` blog, `127.0.0.1:8080` API, `192.168.1.120:9000` intranet drive):
1. Keep this one `openflared` client online.
2. Create three separate website configs in the admin panel (each bound to its own public domain).
3. Select **the same tunnel** as the origin mode for all three.
4. Fill in the corresponding different ports or LAN IPs in each intranet target address (e.g. `127.0.0.1:80`, `127.0.0.1:8080`, `192.168.1.120:9000`).
5. Publish and activate — one tunnel, many uses.
### 2. Seamless Integration with Gateway Security Features
### 2. Gateway Security Features Stack Seamlessly
Since all public traffic enters the public Agent node first, completing the HTTPS/TLS handshake and WAF filtering before traveling through the secure tunnel:
Because all public traffic first enters the public Agent node — HTTPS/TLS handshake and WAF engine interception happen there — then travels through the secure tunnel to the intranet:
Your intranet services **naturally benefit from the following advanced features without any code changes**:
* **One-Click HTTPS**: Select or issue SSL certificates directly in the management console, encrypting transmission end-to-end.
* **Global/Custom WAF Protections**: Enables SQL injection blocking, XSS prevention, and regional IP filtering.
* **Human-Machine Challenge (PoW CC)**: Instantly blocks brute-force CC API attacks targeting your intranet services.
Your intranet service needs **zero modification** to enjoy:
* **One-click HTTPS**: select or apply an SSL certificate for the domain directly in the admin panel; data is encrypted end-to-end.
* **Global/custom WAF protection**: enable SQL injection blocking, XSS injection defense, and malicious geo-IP blocking.
* **Human challenge (CC PoW)**: one-click defense against malicious CC requests to intranet APIs.
---
## Common Troubleshooting
## Troubleshooting
### 1. Tunnel Shows as "Offline" in the Management Console
### 1. Tunnel shows「Offline」in the admin panel
* **Check the Token**: Check if the `tunnel_token` configured in `flared` logs or environment variables matches the one generated in the management console.
* **Check Outbound Connectivity**: The intranet server must be able to make outbound requests to the Server address. Ensure the control plane firewall is not blocking HTTP requests from the client.
* **Relay Firewall Port Closed**: Check if port `7000` (or your custom bindPort) on the public Relay node has been allowed in the public security groups.
* **Check the Token**: verify the `tunnel_token` in `flared` logs or env vars matches the one generated in the admin panel.
* **Check network connectivity**: the intranet server must be able to reach the Server address over outbound connections.
* **Relay firewall not open**: check that the relay node's public `7000` port (or custom `relay_bind_port`) is opened to the public in the security group.
### 2. Accessing the Public Domain Returns 502 Bad Gateway / 504 Gateway Timeout
### 2. Public domain returns 502 Bad Gateway / 504 Gateway Timeout
* **Intranet Service Not Running**: Verify that the service corresponding to the intranet target address is running and listening on the intranet server.
* **Target Address Unreachable**: If the intranet address is set to `127.0.0.1:8080`, ensure the service is running on the exact same host as `openflared`; if set to a LAN IP `192.168.x.x`, test connectivity to that IP inside the `openflared` container.
* **Check Client Application Logs**: View the "Apply Logs" in the management console or inspect local `flared` logs for any `LastError`. When frpc fails to connect to the intranet port, it reports the failure details to the Server.
* **Intranet service not running**: confirm the service at the intranet target address is started and listening on the intranet server.
* **Target address unreachable**: if the intranet address is `127.0.0.1:8080`, ensure the service runs on the same host as `openflared`; if it's a LAN IP `192.168.x.x`, test LAN reachability from inside the `openflared` container.
* **Check node state and logs**: view the Tunnel node detail and「Apply Records」in the admin panel; frpc process errors are logged in detail in the `flared` logs on the intranet host.
### 3. Multiple Relays Network Instability or Retry Failures
### 3. Multi-relay network flapping or retry failures
* When the control plane associates multiple Relay nodes, `openflared` spawns independent frpc daemon processes for each Relay and pulls topology states periodically at `sync_interval` (default 30s) configured in `flared.json`.
* If a Relay drops frequently due to network jitter, the system triggers the backoff retry mechanism automatically. You can see `frpc process missing, starting` logs on the host, which is a normal process self-healing action and will recover within 5-10 seconds after network recovery.
* When the control plane is associated with multiple Relay nodes, `openflared` spawns a separate frpc supervisor per Relay and periodically pulls topology state from the control plane within the `sync_interval` configured in `flared.json` (default 30s).
* If a relay node frequently drops due to network jitter, the system auto-triggers exponential backoff retries (initial 1s, cap 60s). You may see `frpc process missing, starting` in the host logs — that's normal process self-healing; it reconnects automatically after the network recovers.
+95 -68
View File
@@ -1,100 +1,113 @@
# WAF Auto IP Group Expressions
# WAF Auto IP Group Rule Syntax
Automatic IP groups are used to aggregate metrics from request logs on a per-client-IP basis, using Expr expressions to determine if an IP should be added to the group. Automatic IP groups can be referenced by IP blacklists or whitelists in WAF rule groups; during publication, the Server only writes the referenced IP group ID to `waf_config.json`, while IP group members are synchronized independently by the Agent into the local runtime files.
Auto IP groups aggregate metrics per client IP from request logs, then use Expr expressions to decide whether to add an IP to the group list. Auto IP groups can be referenced by a WAF rule group's IP blocklist or allowlist; on config release, the Server only writes the IP group reference IDs into `waf_config.json` — IP group members are synced independently by the Agent to the local runtime file.
## Configuration Structure
## Config Structure
The configuration of an automatic IP group is a JSON object:
An auto IP group config is a JSON object:
```json
{
"lookback_minutes": 60,
"lookback": "1h",
"rules": [
{
"name": "Single IP High-Frequency 404 Scanning",
"expr": "request_count > 100 && status_404_ratio >= 0.8"
"name": "Single-IP high-frequency 404 scanning",
"expr": "request_count > 100 && StatusRatio(404) >= 0.8"
}
]
}
```
Field Descriptions:
Field reference:
| Field | Type | Role |
| Field | Type | Purpose |
| --- | --- | --- |
| `lookback_minutes` | number | How many minutes of request logs to look back during execution. Defaults to 60 minutes if blank, minimum 5 minutes, maximum 43200 minutes. |
| `rules` | array | List of automatic rules. If any rule matches, the IP is added to the automatic IP group list. |
| `rules[].name` | string | Rule name, used only for UI display and error messages. |
| `rules[].expr` | string | Expr expression, must return a boolean value. |
| `lookback` | string | lookback window duration in Go Duration syntax, e.g. `30m`, `1h`, `90m`. Defaults to `1h`, max 30 days. Compatible with the legacy `lookback_minutes` (integer minutes). |
| `rules` | array | auto-rule list. If any rule matches, the IP enters the auto IP group list. |
| `rules[].name` | string | rule name, only for UI display and error messages. |
| `rules[].expr` | string | Expr expression, must return a boolean. |
## Evaluation Mechanics
## Execution Semantics
Automatic rules do not evaluate logs request-by-request, but instead aggregate them by client IP first:
Auto IP groups first aggregate metrics per client IP, then run rule expressions against each IP:
1. The Server reads request logs from the past `lookback_minutes` minutes.
2. Groups them by normalized IP (`remote_addr`).
3. Computes metrics like request count, 404 count, and direct IP host count for each IP.
4. Evaluates `rules[].expr` for each IP.
5. If an IP matches any rule, it is written to the automatic IP group's IP member list.
1. The Server reads request logs from the last `lookback` window.
2. Groups by normalized `remote_addr` IP.
3. Computes per-IP metrics: request count, 404 count, direct-IP Host count, etc.
4. Runs `rules[].expr` per IP.
5. If an IP matches any rule, it is written into the auto IP group's `IP / IP segment` list.
Whether a request is "accessing via IP directly" is determined by the `Host` field in the request logs. If the Host header is an IPv4 or IPv6 literal (e.g., `203.0.113.10`, `[2001:db8::10]`, `203.0.113.10:443`), it is counted in `ip_host_count`.
Whether a Host is "accessed via IP" is judged by the `Host` field in request logs: if the Host is an IPv4 or IPv6 literal, e.g. `203.0.113.10`, `[2001:db8::10]`, `203.0.113.10:443`, it counts toward `ip_host_count`.
## Available Metrics
## Available Keywords
The following metrics are directly available in Expr expressions:
The expression can use these fields directly:
| Keyword | Type | Role |
| Keyword | Type | Purpose |
| --- | --- | --- |
| `ip` | string | The client IP currently being evaluated. |
| `request_count` | number | Total request count of the IP in the lookback window. |
| `status_404_count` | number | Number of 404 responses returned to the IP in the lookback window. |
| `status_404_ratio` | number | 404 request ratio, calculated as `status_404_count / request_count`. |
| `ip_host_count` | number | Number of requests from the IP using an IP address directly as the Host header. |
| `ip_host_ratio` | number | Ratio of direct IP address accesses, calculated as `ip_host_count / request_count`. |
| `client_error_count` | number | Number of requests returning 4xx status codes. |
| `server_error_count` | number | Number of requests returning 5xx status codes. |
| `last_seen_unix` | number | Unix timestamp (in seconds) of the last request from the IP in the lookback window. |
| `ip` | string | the client IP currently being judged. |
| `request_count` | number | the current IP's total requests in the lookback window. |
| `status_404_count` | number | the current IP's requests returning 404 in the window. |
| `status_404_ratio` | number | 404 ratio, computed as `status_404_count / request_count`. |
| `ip_host_count` | number | requests where the current IP accessed via an IP-literal Host. |
| `ip_host_ratio` | number | ratio of IP-address access, computed as `ip_host_count / request_count`. |
| `client_error_count` | number | the current IP's requests returning 4xx. |
| `server_error_count` | number | the current IP's requests returning 5xx. |
| `last_seen_unix` | number | the current IP's last request Unix timestamp (seconds) in the window. |
All ratio fields are decimals between `0` and `1`. An 80% ratio should be written as `0.8`, and 50% as `0.5`.
Ratio fields are decimals between `0` and `1`. 80% is written `0.8`, 50% is `0.5`.
## Common Expr Syntax
### Custom Status Code Matching
Automatic IP groups use the Expr syntax. The expression must return a boolean value.
If the built-in `status_404_count` / `status_404_ratio` don't fit, use these built-in methods to match arbitrary status codes:
Common Operators:
* **`StatusCount(code)`**: request count of the current IP returning the given status code (or class) in the window.
* exact code: `StatusCount(403) > 10`
* status class (`1xx`–`5xx`, case-insensitive): `StatusCount("4xx") > 50`
* **`StatusRatio(code)`**: the ratio of the above count to the IP's total requests.
* exact code: `StatusRatio(502) >= 0.5`
* status class: `StatusRatio("4xx") >= 0.8`, `StatusRatio("5xx") >= 0.3`
| Operator | Role | Example |
A status class aggregates all codes in that hundred range, e.g. `"4xx"` covers 400–499, `"2xx"` covers 200–299.
## Common Expr Patterns
Auto IP groups use Expr syntax; the expression must return a boolean.
Common operators:
| Pattern | Purpose | Example |
| --- | --- | --- |
| `>`, `>=`, `<`, `<=` | Numeric comparison | `request_count > 100` |
| `==`, `!=` | Equality / Inequality | `ip != "127.0.0.1"` |
| `&&` | Logical AND | `request_count > 100 && status_404_ratio >= 0.8` |
| `||` | Logical OR | `status_404_ratio >= 0.8 || server_error_count > 20` |
| `!` | Logical NOT | `!(ip == "127.0.0.1")` |
| `in` | Value is in list | `ip in ["203.0.113.10", "198.51.100.20"]` |
| `not in` | Value is not in list | `ip not in ["127.0.0.1"]` |
| `()` | Grouping controls operator priority | `(request_count > 100 && status_404_ratio >= 0.8) || server_error_count > 50` |
| `>`、`>=`、`<`、`<=` | numeric comparison | `request_count > 100` |
| `==`、`!=` | equal / not equal | `ip != "127.0.0.1"` |
| `&&` | and | `request_count > 100 && StatusRatio(404) >= 0.8` |
| `||` | or | `StatusRatio(404) >= 0.8 || server_error_count > 20` |
| `!` | negation | `!(ip == "127.0.0.1")` |
| `in` | value in list | `ip in ["203.0.113.10", "198.51.100.20"]` |
| `not in` | value not in list | `ip not in ["127.0.0.1"]` |
| `()` | grouping precedence | `(request_count > 100 && StatusRatio(404) >= 0.8) || server_error_count > 50` |
## Built-in Presets
The management console provides two built-in preset rules that can be added directly and adjusted as needed:
The admin panel ships two preset rules that can be added and then adjusted:
```json
{
"name": "Single IP High-Frequency 404 Scanning",
"expr": "request_count > 100 && status_404_ratio >= 0.8"
"name": "Single-IP high-frequency 404 scanning",
"expr": "request_count > 100 && StatusRatio(404) >= 0.8"
}
```
Meaning: A single IP requests more than 100 times in the lookback window, and the 404 status code ratio is at least 80%.
Meaning: a single IP has over 100 requests in the window and a 404 ratio of at least 80%.
```json
{
"name": "Single IP Direct IP Access Mismatch",
"name": "Single-IP direct-access anomaly",
"expr": "ip_host_count > 50 && ip_host_ratio > 0.5"
}
```
Meaning: A single IP accesses the server directly using an IP address as the Host header more than 50 times, and this type of access represents more than 50% of its total requests.
Meaning: a single IP accessed via IP-literal Host over 50 times, and that access ratio exceeds 50%.
## Examples
@@ -102,60 +115,74 @@ High-frequency 404 scanning:
```json
{
"lookback_minutes": 60,
"lookback": "1h",
"rules": [
{
"name": "High-Frequency 404 Scanning",
"expr": "request_count > 100 && status_404_ratio >= 0.8"
"name": "High-frequency 404 scanning",
"expr": "request_count > 100 && StatusRatio(404) >= 0.8"
}
]
}
```
Direct IP access mismatch:
IP direct-access anomaly:
```json
{
"lookback_minutes": 30,
"lookback": "30m",
"rules": [
{
"name": "Direct IP Access Mismatch",
"name": "IP direct-access anomaly",
"expr": "ip_host_count > 50 && ip_host_ratio > 0.5"
}
]
}
```
Capture both high 4xx and 5xx errors:
Catching both high 4xx and high 5xx:
```json
{
"lookback_minutes": 120,
"lookback": "2h",
"rules": [
{
"name": "Abnormal Error Rates",
"name": "Abnormal error rate",
"expr": "(client_error_count > 80 && request_count > 100) || server_error_count > 30"
}
]
}
```
Exclude trusted IPs:
Using status-class syntax (equivalent thinking to `client_error_count` / `server_error_count`):
```json
{
"lookback_minutes": 60,
"lookback": "2h",
"rules": [
{
"name": "404 Scanning Excluding Trusted IPs",
"expr": "ip not in [\"203.0.113.10\", \"198.51.100.20\"] && request_count > 100 && status_404_ratio >= 0.8"
"name": "High 4xx or 5xx ratio",
"expr": "request_count > 100 && (StatusRatio(\"4xx\") >= 0.8 || StatusRatio(\"5xx\") >= 0.3)"
}
]
}
```
## Usage Recommendations
Excluding trusted IPs:
Start with a shorter lookback window and higher thresholds to monitor matches, then adjust thresholds gradually. The IP Groups page in the management console allows you to click **"Test Rule"** before saving to view matching IPs in the current window immediately. Once an automatic IP group runs, it overwrites the list of IPs. If you want to permanently whitelist or blacklist certain IPs, add them to a manual IP group instead, and reference both manual and automatic groups in your WAF rule groups.
```json
{
"lookback": "1h",
"rules": [
{
"name": "404 scanning excluding trusted IPs",
"expr": "ip not in [\"203.0.113.10\", \"198.51.100.20\"] && request_count > 100 && StatusRatio(404) >= 0.8"
}
]
}
```
Updating automatic IP groups does not require publishing configuration versions. Online Agents receive changes via WebSocket and update the local `waf_ip_groups.json` instantly. If WebSocket is unavailable, the Agent reports its local checksum in heartbeats, and the Server syncs only the mismatched IP groups.
## Usage Tips
Start with a shorter lookback and higher thresholds to observe hits, then tune gradually. The admin IP group page supports clicking **Test Rule** before saving to directly view the IPs hit in the current window; when the auto IP group actually runs, it overwrites the group's IP list. To keep certain addresses long-term, put them in a manual IP group and reference both the manual and auto groups in the WAF rule group.
Auto IP group updates don't require republishing a config version. Online Agents receive the changed IP groups via WebSocket and update the local `waf_ip_groups.json`; when the WebSocket is unavailable, the Agent reports the local IP group checksum in the next heartbeat and the Server only returns the groups whose checksums differ.
+18 -149
View File
@@ -1,162 +1,31 @@
# WAF Security Protection
You will learn: How the OpenFlare edge Web Application Firewall (WAF) works, its protection dimensions, how to manage and reference the three types of IP groups (Manual, Subscription, and Expr-based Automatic IP groups), configure CC protection challenges (PoW human-machine verification) and regional filtering, and achieve sub-second hot updates of IP group members without Nginx reloads.
OpenFlare WAF orchestrates rules with a visual directed acyclic graph. When creating a rule you only enter a name; the system creates the default "start → pass" graph and enters the editor.
---
## Nodes and Connections
## Core Concepts
- **Start**: unique per rule; enters the graph along `next`.
- **Pass**: ends the current rule; if the route has more rules, execution continues.
- **Block**: immediately terminates the request with the configured status code and HTML response.
- **IP match**: configure an IP, CIDR, or IP group; connect `true` / `false` respectively.
- **Geo match**: branch by country or ISO 3166-2 first-level division code; the country list shows both localized names and codes; divisions are searchable by country name, division name, or code. Country and City MMDB are provided by disk files (Docker images COPY them to the default path; bare binaries download from the configured URL on first startup) and update on the configured cycle. When City MMDB is unavailable, it is treated as no match.
- **PoW**: takes over the request when the challenge is incomplete; after verification passes, continues along `next`.
Before configuring security policies, you need to understand the core components of the WAF:
The server rejects cycles, dangling outlets, unreachable nodes, duplicate port connections, and invalid configs. Save carries the `revision` obtained at page load; on a 409 conflict, reload to avoid overwriting others' changes.
| Concept | Description | Scope & Activation Method |
| --- | --- | --- |
| **WAF Rule Group (Rule Group)** | A logical collection of security rules, including: IP whitelists/blacklists (direct input or IP group references), country/region limits, CC protection (PoW), and custom block responses. | Supports global enablement or binding to single/multiple websites. **Modifying rule group definitions requires publishing and activating a configuration version**. |
| **IP Group (IP Group)** | A list container storing individual IPs or CIDR blocks. Divided into **Manual**, **Subscription**, and **Automatic** types. WAF rule groups reference IP groups by ID. | Belongs to dynamic resources. **IP group member updates support sub-second WebSocket hot-syncing, completely bypassing Nginx process reloads**. |
| **PoW Challenge (CC PoW)** | A human-machine verification challenge based on Proof of Work. By prompting browsers to solve hash collisions of a specified difficulty, it silently blocks malicious brute-force scripts and bots while keeping legitimate user experience smooth. | A configuration Tab in the rule group. **Modifying PoW parameters requires publishing and activating a configuration version**. |
Select a normal node or edge, then click the delete button at the canvas top-right, or press Delete/Backspace. Deleting a node also deletes associated edges; the single "start" and "pass" nodes cannot be deleted. Dragging a node only records the final coordinates on release; the canvas state is not rebuilt repeatedly during movement.
---
The right node property panel is hidden by default and shows after clicking a node; it auto-collapses when clicking an edge or blank canvas.
## Recommended Configuration Sequence
The orchestration area defaults to a compact height and smaller initial zoom; you can still zoom freely with the wheel or canvas Controls.
When configuring security protections for your websites, we recommend doing so in the following order:
## Binding and Effect
1. Navigate to IP Groups, creating the required **Manual IP Groups** (e.g., developer whitelist) or **Automatic IP Groups** (e.g., auto-blocked IPs based on 404 scans).
2. Create or edit a **WAF Rule Group**:
* Bind the IP groups you want to reference or block.
* Configure regional whitelists/blacklists for countries or provinces.
* (Optional) Configure human-machine challenge parameters in the `PoW` Tab.
* Set custom status codes (e.g., 403, 418) and HTML block pages in the `Block Response` Tab.
3. Associate the rule group with the corresponding **Website Configuration**.
4. Publish and activate the configuration version to let the edge node (Agent) apply the WAF rules to filter traffic.
Enabled global rules always execute first; route-bound custom rules execute strictly in list order. After adjusting the order, you must publish a config version for the rule topology to take effect with the OpenResty reload.
---
IP group members are dynamic resources. The Agent checks the checksum every 5 seconds and updates in-memory snapshots across workers on change — no rule republish or reload needed. Manual, subscription, and auto IP groups can all be referenced by IP-match nodes. A single full IP group runtime snapshot is capped at 20 MiB; exceeding the cap makes publish or sync return an error and keeps using the previous valid snapshot.
## Detailed Step Guide
> [!IMPORTANT]
> When upgrading from the legacy fixed allow/block-list, geo, or PoW forms, rule graphs reset to "start → pass" and old policy fields are not migrated. Re-orchestrate and verify each rule before publishing a new version.
### Step 1: Manage and Configure IP Groups
IP groups are the foundations of large-scale IP filtering. OpenFlare provides three highly resilient types of IP groups:
#### 1. Manual IP Groups (Manual)
* **Purpose**: Statically maintain a list of verified trusted IPs or long-term blocked IPs/CIDR blocks.
* **Configuration**: Click "Create IP Group" -> select type "Manual" -> enter IPs or CIDRs line-by-line (e.g., `192.168.1.100` or `10.0.0.0/24`).
#### 2. Subscription IP Groups (Subscription)
* **Purpose**: Integrate third-party threat intelligence databases or IP ranges published by cloud providers.
* **Configuration**: Select type "Subscription" -> enter fetch URL (supports line-separated plain text or standard JSON formats). A background cron job on the Server periodically pulls the subscription source and updates the group members automatically.
#### 3. Automatic IP Groups (Automatic)
* **Purpose**: **The most aggressive automated defense channel against scans and brute-force attacks**.
* **Configuration**: Select type "Automatic" -> write Expr log aggregation logic. You can directly select built-in presets:
* **Single IP High-Frequency 404 Scanning**: `request_count > 100 && status_404_ratio >= 0.8` (A single IP requesting over 100 times in the past hour with a 404 response ratio of at least 80%).
* **Single IP Direct IP Access Mismatch**: `ip_host_count > 50 && ip_host_ratio > 0.5` (Bypassing domains to hit the server directly using IP address host headers).
* **Test & Run**: Click **"Test Rule"** before saving to preview IPs matching the current log window. Click **"Execute Now"** after saving to aggregate logs immediately and generate the block list.
> [!TIP]
> For the detailed syntax and available metrics of automatic IP groups, see [WAF Auto IP Group Expressions](./waf-ip-group-expr.md).
---
### Step 2: Create and Configure a WAF Rule Group
1. Navigate to the **"WAF"** section in the side menu, and click **"Create Rule Group"**.
2. Enter the rule group name (e.g., `production-api-shield`), and select if it is a "Global Rule Group".
3. Enter rule group details, and configure the tabs sequentially below:
#### 1. Whitelist / Blacklist Configuration (Allow / Block Lists)
* **Direct IPs**: Enter individual IPs or CIDR blocks line-by-line that need temporary whitelisting or blacklisting directly in the text area.
* **IP Group Reference**: Click "Bind IP Groups", selecting the manual, automatic, or subscription IP groups you configured in Step 1. Whitelists permit traffic instantly, whereas blacklists block it.
#### 2. Regional Restriction (GeoIP)
* **Description**: OpenFlare integrates GeoIP geolocation resolution.
* **Configuration**: Toggle the regional restriction switch, selecting "Allow Only" or "Block".
* * For example, if your service is only intended for domestic users, set the mode to "Allow Only" and check `China` in the country list.
* * Supports refining to specific provinces/regions, enabling you to block malicious traffic originating from targeted geographic zones with one click.
#### 3. Human-Machine Challenge Configuration (PoW CC Protection)
* **Description**: Enable CC protection human-machine challenges. When a request triggers the CC protection threshold, the browser renders a silent challenge page, solving a mathematical challenge (hash collision) within several hundred milliseconds. Upon passing, it sets a Cookie and allows subsequent visits. This is seamless to actual users but blocks brute-force scripts and CC tools that do not support JS execution or mathematical computations.
* **Core Parameters**:
* **Status**: Enable / Disable.
* **Hash Difficulty**: Controls the computation difficulty (recommending `4` or `5`).
* **Cookie Expiration**: How long the verification remains valid after passing (e.g., `3600` seconds).
* **Custom Challenge HTML**: Customize the Loading page style of the challenge to match your business design.
#### 4. Block Response (Block Response)
* **Description**: Define the behavior of the WAF when blocking malicious requests.
* **Configuration**:
* **Block Status Code**: Customize the HTTP status code returned, e.g., the standard `403` or a fun `418 (I'm a teapot)`.
* **Block Response Body**: Input custom HTML content shown to blocked attackers (e.g., "WAF Interception: Your request has been logged").
---
### Step 3: Associate the Rule Group with Websites
Once configured, the rule group does not automatically take effect; you need to bind it to specific website configurations.
* **Option A (Recommended)**: In the **"Bind Websites"** Tab of the rule group details, select the websites you wish to apply this rule group to and save.
* **Option B**: Return to **"Website Configuration"**, edit a specific website, and check and bind the rule group in the "Security Protection" section.
> [!NOTE]
> If a rule group is marked as **"Global Rule Group (is_global)"**, it applies to **all websites** hosted on the gateway automatically, requiring no manual binding.
---
### Step 4: Publish & Activate Configurations
1. If you modify **rule group definitions**, **GeoIP scopes**, **PoW CC difficulties**, or **website-to-rule-group bindings**:
* Click **"Preview Config"** -> **"Publish & Activate"** in the top right corner.
* Once the Agent pulls and validates the new version, it rewrites local core OpenResty config files (`waf_config.json`, etc.) and gracefully reloads the processes to apply the policies.
2. If you only update **IP group members** (e.g., adding/deleting an IP in a manual IP group, or an automatic IP group aggregates a new set of blocked IPs periodically):
* **No publication or activation is required!**
* The Server calculates the new MD5 Checksum of the IP group immediately after updating the database.
* The control plane **broadcasts the modified IP group members in real-time to all online Agents via WebSocket**. The Agent overwrites the runtime local disk file `waf_ip_groups.json` incrementally.
* The OpenResty Lua engine calculates the file hash in microseconds when processing new requests. If it detects a Checksum change, it reloads it into the memory dictionary (`ngx.shared`) in real-time. **This entire process requires absolutely no Nginx service reloads, having zero impact on online high-concurrency operations**.
* Even if the WebSocket connection drops, the Agent reports its local Checksum in every heartbeat cycle, and the Server syncs the differential updates to guarantee synchronization.
---
## WAF Evaluation Flow (Filtering Funnel)
When an external request reaches the OpenResty data plane, the WAF runtime evaluates it in the `access` phase according to the funnel decision chain below. Once a match is made, evaluation terminates:
```text
Request enters access phase
│
v
Get all active rule groups bound to this site (Global + Bound Custom groups)
│
v
1. Matches IP whitelist / Whitelist IP group? ──────(Yes)─────► [ Allow (ALLOW) ]
│ (No)
v
2. Matches country / province whitelist? ────────(Yes)─────► [ Allow (ALLOW) ]
│ (No)
v
3. Matches IP blacklist / Blacklist IP group? ──────(Yes)─────► [ Block (BLOCK) ] ──► Return status & HTML block page
│ (No)
v
4. Matches country / province blacklist? ────────(Yes)─────► [ Block (BLOCK) ] ──► Return status & HTML block page
│ (No)
v
5. Is PoW CC protection enabled for this site?
├───(Yes)───► [ Validate PoW Cookie ] ──(Passed)──► [ Allow (ALLOW) ]
│ │
│ (Not Passed)
│ v
│ [ Render PoW Challenge ] ──(Solved)──► Set Cookie & Allow
v
6. No rules triggered, legitimate traffic ─────────────────────► [ Allow (ALLOW) ]
```
---
## Best Practices & Tuning Recommendations
* **Whitelist Precedence & Protection**: Before deploying strict blacklists or regional blocks, we strongly recommend creating a "Trusted IP Group" containing your team's office egress IPs, local development IPs, and third-party callback server IPs (e.g., WeChat or Alipay payment callback addresses), and prioritizing it in the rule group's **whitelist**. This effectively prevents accidental blockages.
* **Reasonably Fine-tune PoW Difficulty**: Human-machine CC challenge hash difficulty (`challenge_difficulty`) is a double-edged sword:
* Difficulty `3`: Computes almost instantly, providing low protection.
* Difficulty `4`: Normal phones/low-end browsers solve it in 100-300ms, providing good protection.
* Difficulty `5`: Requires 500ms-2s, providing strong protection but low-end client browsers might perceive slight loading delays.
* Difficulty `6` and above: Computes exponentially slower, easily freezing client browser CPUs. **We strongly recommend choosing `4` or `5` in production**.
* **Utilize "Test Rule"**: For automatic IP groups, always click **"Test Rule"** before saving. By inspecting the list of matching IPs in the current window, verify if your Expr expressions thresholds (such as request counts, 404 ratios, etc.) are too broad or too strict, preventing accidental blockages of legitimate users.
* **Isolate Static & Dynamic Blacklists**: Never enter static malicious IPs that require permanent blocks directly into automatic IP groups (since the aggregated list will be overwritten in the next cron cycle). You should add permanent malicious IPs into a dedicated "Manual Blacklist IP Group" and reference both the manual and automatic groups in your rule groups.
Architecture, graph validation, and failure rollback details: [WAF Orchestration Rule Design](../design/waf-orchestration-design.md).
+67 -49
View File
@@ -1,111 +1,129 @@
# CLI Commands
# Commands & Scripts
You will learn: Common commands for starting, building, testing, installing, and uninstalling the OpenFlare Server, Admin Frontend, Agent, Swagger, and Documentation site.
You will learn: common start, build, test, install, and uninstall commands for the OpenFlare Server, admin frontend, Agent, Relay, OpenFlared, Swagger, and the docs site.
> All commands run at the **repo root** unless noted otherwise.
## Server
Start from source:
Source startup:
```bash
cd openflare-server
export SESSION_SECRET='replace-with-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
go run .
cp config.example.yaml config.yaml
go run main.go all
```
Specify listening port and logging directory:
Split processes:
```bash
go run . --port 3000 --log-dir ./logs
go run main.go api # HTTP API only
go run main.go worker # Asynq Worker only
go run main.go scheduler # scheduled tasks only
```
Run tests:
Build the binary:
```bash
make build-backend
# output: bin/openflare-server
```
Tests:
```bash
cd openflare-server
GOCACHE=/tmp/openflare-go-cache go test ./...
```
## Frontend
Development:
Quality gate:
```bash
cd openflare-server/web
make code-check
```
Auto-format backend Go source (organize imports) and frontend source:
```bash
make format
```
This command uses `goimports` to organize backend Go imports and the repo-pinned Prettier version to format `frontend/` source; build artifacts, dependencies, public static assets, and lock files are ignored.
## Frontend
Dev:
```bash
cd frontend
pnpm install
pnpm dev
```
Build static assets:
Build the embedded artifact (hosted by the Go Server):
```bash
cd openflare-server/web
pnpm build
cd frontend
pnpm build:embed
# or at repo root: make build-embedded
```
Linting and testing checks:
Checks:
```bash
cd openflare-server/web
cd frontend
pnpm lint
pnpm typecheck
pnpm test
pnpm tsc --noEmit --jsx preserve
pnpm check:i18n
```
## Agent
Run from source:
Source run:
```bash
cd openflare-agent
go run ./cmd/agent -config /path/to/agent.json
```
Compile:
Build:
```bash
cd openflare-agent
go build -o openflare-agent ./cmd/agent
make build-agent
# or: go build -o bin/openflare-agent ./cmd/agent
```
Run tests:
Tests:
```bash
cd openflare-agent
GOCACHE=/tmp/openflare-go-cache go test ./...
GOCACHE=/tmp/openflare-go-cache go test ./internal/apps/agent/...
```
## Relay (Server-side)
## Relay
Run from source:
Source run:
```bash
cd openflare-relay
go run ./cmd -config /path/to/relay.json
go run ./cmd/relay -config /path/to/relay.json
```
Compile:
Build:
```bash
cd openflare-relay
go build -o openflare-relay ./cmd
make build-relay
# or: go build -o bin/openflare-relay ./cmd/relay
```
## OpenFlared (Client-side)
## OpenFlared (Tunnel client)
Run from source:
Source run:
```bash
cd openflared
go run ./cmd -config /path/to/flared.json
go run ./cmd/flared -config /path/to/flared.json
```
Compile:
Build:
```bash
cd openflared
go build -o openflared ./cmd
make build-flared
# or: go build -o bin/flared ./cmd/flared
```
## Install Agent
@@ -124,14 +142,14 @@ curl -fsSL https://raw.githubusercontent.com/Rain-kl/OpenFlare/main/scripts/unin
## Swagger
Regenerate Swagger documentation:
Regenerate the Swagger docs:
```bash
go install github.com/swaggo/swag/cmd/swag@v1.16.4
cd openflare-server
swag init -g main.go -o docs
make swagger
```
Access: `http://localhost:3000/api/swagger/index.html` (default `api_prefix` `/api`; only mounted in non-production)
## Docs
Local preview:
+365 -328
View File
@@ -1,370 +1,407 @@
# Configuration Options
# Configuration
You will learn: What configuration sources are supported by OpenFlare Server, frontend builds, and Agents; what the default configuration values are; and how to configure common deployment combinations.
You will learn: which config sources OpenFlare Server, frontend build, Agent, Relay, and OpenFlared support, what config fields and env vars exist, and their defaults and behavior.
This document aggregates the currently supported configuration options for OpenFlare Server and Agent in version `1.0.0`, keeping only running parameters that are currently active.
This document summarizes all configuration items supported by the current OpenFlare version.
## Configuration Sources
---
The Server supports three types of configuration sources:
## Config Sources
1. CLI arguments.
2. Environment variables.
3. Runtime configurations in the database `options` table.
### 1. Server Config Sources
- **Config file**: reads `config.yaml` in the same directory at startup by default (overridable via the `CONFIG_PATH` env var).
- **Env vars**: every field in the config file can be overridden by an `UPPER_SNAKE_CASE` env var (env vars take precedence over `config.yaml`).
- **System runtime config**: stored in the `w_system_configs` table in the relational DB. These can be hot-updated and take effect dynamically via the admin UI or system API.
The Agent supports:
### 2. Agent / Relay / OpenFlared Config Sources
- **CLI args**: `-config` specifies the config file (JSON format).
- **Config file**: e.g. `agent.json`, `relay.json`, `flared.json`.
- **Override env vars**: specific env vars can override connection addresses and Token credentials in the config file.
1. The `-config` CLI argument.
2. The `agent.json` configuration file.
3. A small set of environment variables for overriding logs and settings.
---
The Relay (Server-side) supports:
## Config File Locations
1. The `-config` CLI argument.
2. The `relay.json` configuration file.
3. Persistent environment variables for overriding runtime flags.
The Client (Intranet Client) supports:
1. The `-config` CLI argument.
2. The `flared.json` configuration file.
3. Startup overrides and logging environment variables.
## Configuration File Locations
| Component | Default Location | Description |
| Component | Default Location | Notes |
| --- | --- | --- |
| Server SQLite | `openflare.db` | Can be customized via `SQLITE_PATH` |
| Agent Config | `./agent.json` | Can be specified via `-config` |
| One-Click Agent | `/opt/openflare-agent/agent.json` | Generated by the installation script by default |
| Agent Data Dir | `data` in the config folder | Can be customized via `data_dir` |
| Relay Config | `./relay.json` | Can be specified via `-config` |
| One-Click Relay | `/opt/openflare-relay/relay.json` | Generated by the installation script by default |
| Client Config | `./flared.json` | Can be specified via `-config` |
| One-Click Client | `/opt/openflared/flared.json` | Generated by the installation script by default |
| Server config file | `./config.yaml` | overridable via `CONFIG_PATH` |
| Server SQLite DB | `openflare.db` | overridable via `database.sqlite_path` / `SQLITE_PATH` |
| Agent config file | `./agent.json` | overridable via `-config` |
| One-click install Agent config | `/opt/openflare-agent/agent.json` | default path generated by the install script |
| Agent data dir | `data` next to the config file | overridable via `data_dir` |
| Relay config file | `./relay.json` | overridable via `-config` |
| One-click install Relay config | `/opt/openflare-relay/relay.json` | default path generated by the install script |
| Client config file | `./flared.json` | overridable via `-config` |
| One-click install Client config | `/opt/openflared/flared.json` | default path generated by the install script |
## Server CLI Arguments
---
## Server CLI Args
```bash
cd openflare-server
go run . --port 3000 --log-dir ./logs
# specify a config file when starting the Server
CONFIG_PATH=/path/to/custom-config.yaml ./openflare-server all
```
| Argument | Description | Default Value |
| --- | --- | --- |
| `--port` | Port the Server listens on | `3000` |
| `--log-dir` | Directory to output logs | Empty (stdout) |
| `--version` | Outputs current version and exits | `false` |
| `--help` | Outputs help information and exits | `false` |
Supported sub-service commands (fused/single-process mode):
- `all`: starts all services in one process (API + Worker + Scheduler, default).
- `api`: starts only the API service for the admin panel and node communication.
- `worker`: starts only the background-task Worker service.
- `scheduler`: starts only the scheduled-task Scheduler service.
## Server Environment Variables
---
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `PORT` | Port the Server listens on | `3000` |
| `GIN_MODE` | Gin framework running mode | Defaults to release unless `debug` |
| `LOG_LEVEL` | Logging level | `info` |
| `SESSION_SECRET` | Session signing key | Randomly generated on startup |
| `SQLITE_PATH` | SQLite database file path | `openflare.db` |
| `DSN` | PostgreSQL DSN (takes precedence over SQLite) | Empty |
| `SQL_DSN` | Legacy PostgreSQL DSN (lower priority than `DSN`) | Empty |
| `REDIS_CONN_STRING` | Redis connection string | Empty |
| `AGENT_TOKEN` | Legacy global Agent Token | Empty |
## Server Env Vars vs Config File
Notes:
All Server core base config is defined in `config.yaml`, and every field supports env-var overrides (env vars take precedence over the YAML file).
* If both `DSN` and `SQL_DSN` exist, `DSN` is prioritized.
* If either `DSN` or `SQL_DSN` coexist with `SQLITE_PATH`, PostgreSQL is prioritized.
* If the target PostgreSQL database is empty and a local SQLite file exists at `SQLITE_PATH`, the Server automatically migrates SQLite data table-by-table on startup.
* `SESSION_SECRET` must be explicitly configured in production.
* If `REDIS_CONN_STRING` is unconfigured, co-located features fall back to in-memory implementations.
## Runtime Options
The following options are maintained in the admin settings page and support hot reloading:
| Parameter | Description | Default Value |
| --- | --- | --- |
| `AgentHeartbeatInterval` | Heartbeat interval for Agents (ms) | `10000` |
| `AgentWebsocketUpgradeEnabled` | Toggles WebSocket upgrades after successful HTTP heartbeat | `true` |
| `NodeOfflineThreshold` | Threshold duration to mark a node offline (ms) | `120000` |
| `AgentUpdateRepo` | GitHub repository for Agent self-updates | `Rain-kl/OpenFlare` |
| `GeoIPProvider` | Geolocation resolution provider | `ipinfo` |
| `DatabaseAutoCleanupEnabled` | Toggles daily automatic cleanup of observability logs | `false` |
| `DatabaseAutoCleanupRetentionDays` | Data retention duration in days, minimum 1 day | `30` |
| `GlobalApiRateLimitNum` / `GlobalApiRateLimitDuration` | Global API rate limit count / window | `300` / `180` |
| `GlobalWebRateLimitNum` / `GlobalWebRateLimitDuration` | Global Web rate limit count / window | `300` / `180` |
| `CriticalRateLimitNum` / `CriticalRateLimitDuration` | Sensitive API rate limit count / window | `100` / `1200` |
Notes:
* When `DatabaseAutoCleanupEnabled` is enabled, the Server deletes `node_access_logs`, `node_metric_snapshots`, and `node_request_reports` daily at 3:00 AM.
* `DatabaseAutoCleanupRetentionDays` must be greater than or equal to 1.
* Leaving retention days blank during a manual trigger in the console deletes all historic logs instantly.
* The GitHub Release in `AgentUpdateRepo` must contain a matching `.sha256` checksum file for every Agent binary (e.g., `openflare-agent-linux-amd64.sha256`); the Agent validates this checksum before replacing the local executable.
* Third-party logins no longer use `GitHubOAuthEnabled`, `GitHubClientId`, and `GitHubClientSecret` as main configuration entrypoints; these legacy options are used only for migrating default GitHub credentials during upgrades.
* The legacy WeChat login options are kept for backward compatibility, but the option page no longer edits them.
* Legacy Cloudflare Turnstile options and validation logic are retained and will work normally.
## OpenResty Parameters
OpenResty performance and caching parameters are managed in the `options` table, including:
* `OpenRestyWorkerProcesses`
* `OpenRestyWorkerConnections`
* `OpenRestyWorkerRlimitNofile`
* `OpenRestyKeepaliveTimeout`
* `OpenRestyProxyConnectTimeout`
* `OpenRestyProxySendTimeout`
* `OpenRestyProxyReadTimeout`
* `OpenRestyProxyBufferingEnabled`
* `OpenRestyGzipEnabled`
* `OpenRestyCacheEnabled`
* `OpenRestyCachePath`
* `OpenRestyCacheMaxSize`
These parameters must be validated, saved, and rendered structurally.
Constraints:
* The console no longer exposes `resolver` settings.
* Upstreams are rendered uniformly as named `upstream` blocks with keepalive enabled.
* Single upstreams carrying a base path or query have their URI correctly appended in `proxy_pass`.
* Multi-upstreams must be pure `scheme://host[:port]` using the same protocol within a single rule.
* `OpenRestyCacheEnabled` enables cache infrastructure and global defaults; the actual caching matching policies (by URL, suffix, or path) are configured per `proxy_routes`.
* The default cache key is `$scheme$host$request_uri`.
* Default `keepalive_timeout` is `20` seconds; default `proxy_connect_timeout` is `3` seconds.
* The default event model is `epoll` with `multi_accept` enabled.
* HTTPS listeners use the independent `http2 on;` directive to avoid deprecation warnings for `listen ... http2` in newer Nginx/OpenResty versions.
## Frontend Build Environment Variables
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `NEXT_PUBLIC_API_BASE_URL` | Base path for frontend API calls | `/api` |
| `NEXT_PUBLIC_APP_VERSION` | Application version shown in the UI | `dev` |
| `NEXT_DEV_BACKEND_URL` | Target backend proxied by the local dev server | `http://127.0.0.1:3000` |
## Agent Environment Variables
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `LOG_LEVEL` | Logging level for the Agent | `info` |
| `OPENFLARE_SERVER_URL` | Server URL; overrides `agent.json` | Empty |
| `OPENFLARE_AGENT_TOKEN` | Node-specific Token; overrides `agent.json` | Empty |
| `OPENFLARE_DISCOVERY_TOKEN` | Auto-registration Token; overrides `agent.json` | Empty |
| `OPENFLARE_NODE_NAME` | Node name; overrides `agent.json` | Empty |
| `OPENFLARE_NODE_IP` | Node IP; overrides `agent.json` | Empty |
| `OPENFLARE_DATA_DIR` | Agent data directory; overrides `agent.json` | Empty |
| `OPENFLARE_OPENRESTY_PATH` | Path to OpenResty binary; overrides `agent.json` | Empty |
| `OPENFLARE_HEARTBEAT_INTERVAL` | Heartbeat interval; overrides `agent.json` | Empty |
| `OPENFLARE_REQUEST_TIMEOUT` | Request timeout; overrides `agent.json` | Empty |
| `OPENFLARE_OPENRESTY_OBSERVABILITY_PORT` | Local observability port; overrides `agent.json` | Empty |
| `OPENFLARE_MMDB_PATH` | WAF GeoIP mmdb path; overrides `agent.json` | Empty |
| `OPENFLARE_MMDB_UPDATE_INTERVAL` | GeoIP mmdb update interval; overrides `agent.json` | Empty |
| `OPENFLARE_MMDB_DOWNLOAD_URL` | GeoIP mmdb download link; overrides `agent.json` | Empty |
## Agent CLI Arguments
| Argument | Description | Default Value |
| --- | --- | --- |
| `-config` | Path to the Agent configuration file | `./agent.json` |
## Agent Configurations Fields
| Field | Description | Required | Default Value / Behavior |
### 1. App Basic Config (`app:`)
| YAML path | Override env var | Description | Default |
| --- | --- | --- | --- |
| `server_url` | Control plane URL | Yes | None |
| `agent_token` | Node-specific access Token | Mutually exclusive with discovery_token | Empty |
| `discovery_token` | Global auto-registration Token | Mutually exclusive with agent_token | Empty |
| `node_name` | Node name | No | Hostname |
| `node_ip` | Node IP | No | Auto-detect, resolves outbound public IP via realip.cc first, falls back to local adapters |
| `openresty_path` | Path to the OpenResty binary | No | `"openresty"` |
| `openresty_observability_port` | Observability port for health checks | No | `18081` |
| `data_dir` | Agent data directory | No | `data` in the config folder |
| `main_config_path` | Write path for Nginx main configuration | No | `data_dir/etc/nginx/nginx.conf` |
| `route_config_path` | Write path for route configurations | No | `data_dir/etc/nginx/conf.d/openflare_routes.conf` |
| `access_log_path` | Write path for OpenResty access logs | No | `data_dir/var/log/openflare/access.log` |
| `cert_dir` | Write directory for SSL certificates | No | `data_dir/etc/nginx/certs` |
| `openresty_cert_dir` | Read directory for certificates in Nginx | No | Same as `cert_dir` |
| `lua_dir` | Write directory for Lua scripts and assets | No | `data_dir/etc/nginx/lua` |
| `openresty_lua_dir` | Read directory for Lua scripts in Nginx | No | Same as `lua_dir` |
| `runtime_config_dir` | Write directory for Agent runtime configs | No | `data_dir/etc/openflare` |
| `mmdb_path` | WAF GeoIP database file path | No | `data_dir/etc/openflare/GeoLite2-Country.mmdb` |
| `mmdb_update_interval` | WAF GeoIP database check interval | No | `86400000` milliseconds |
| `mmdb_download_url` | WAF GeoIP database download URL | No | Built-in GeoLite2 Country URL |
| `observability_buffer_path` | Buffer path for retry metrics logs | No | `data_dir/var/lib/openflare/observability-buffer.json` |
| `observability_replay_minutes` | Lookback window for metric retries | No | `15` |
| `state_path` | Path to store local state JSON file | No | `data_dir/var/lib/openflare/agent-state.json` |
| `heartbeat_interval` | Heartbeat polling interval | No | `10000` milliseconds |
| `request_timeout` | HTTP request timeout duration | No | `10000` milliseconds |
| `app.app_name` | `APP_NAME` | application identifier name | `openflare` |
| `app.env` | `APP_ENV` | runtime env (`development` / `testing` / `production`) | `production` |
| `app.addr` | `APP_ADDR` | service listen address and port | `:3000` |
| `app.node_id` | `APP_NODE_ID` | Snowflake node ID (0-1023); must be unique in multi-instance deploys | `1` |
| `app.api_prefix` | `APP_API_PREFIX` | route prefix for admin panel and API | `/api` |
| `app.graceful_shutdown_timeout` | `APP_GRACEFUL_SHUTDOWN_TIMEOUT` | graceful shutdown wait timeout (seconds) | `30` |
| `app.session_cookie_name` | `APP_SESSION_COOKIE_NAME` | session cookie name | `openflare_session_id` |
| `app.session_secret` | `APP_SESSION_SECRET` | session signature secret; **must be a random long string in production** | none (random) |
| `app.session_domain` | `APP_SESSION_DOMAIN` | shared-session cookie scope domain | empty |
| `app.session_age` | `APP_SESSION_AGE` | browser session lifetime (seconds) | `86400` (24h) |
| `app.session_http_only` | `APP_SESSION_HTTP_ONLY` | enable the cookie's HttpOnly attribute | `false` |
| `app.session_secure` | `APP_SESSION_SECURE` | enable the cookie's Secure attribute (HTTPS) | `false` |
Notes:
* `agent_token` and `discovery_token` cannot both be empty.
* `heartbeat_interval` and `request_timeout` support integer milliseconds or Go duration strings.
* If `AgentWebsocketUpgradeEnabled` is enabled on the Server, the Agent upgrades the HTTP heartbeat to WebSocket; it automatically falls back to HTTP heartbeats if it fails or disconnects.
* If `openresty_path` is left blank, the Agent calls `openresty` on the host.
* Periodic health checks query `http://127.0.0.1:<openresty_observability_port>/openflare/stub_status` instead of executing `openresty -t`; validation prior to reloads, starts, or rollbacks still runs `openresty -t -c <main_config_path>`.
* The Agent initializes and periodically updates `mmdb_path` to support GeoIP region checks; failures to update write warnings and do not disrupt configuration synchronizations.
* The Agent boots normally if `agent.json` is missing but environment variables (`OPENFLARE_SERVER_URL` and a Token) are available; environment variables override JSON settings.
* If `node_ip` is left blank, the Agent resolves its outbound IP via `https://realip.cc` first, which is suitable for Docker/NAT networks.
* If the Agent registers a private `node_ip`, the Server prioritizes saving the public TCP connection IP, preventing NAT adapters from registering internal IPs.
* Enabling "Lock Node IP" in the console retains the manual IP; subsequent Agent registration or heartbeats do not overwrite it.
## Relay Environment Variables
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `LOG_LEVEL` | Logging level for the Relay | `info` |
| `OPENFLARE_SERVER_URL` | Server URL; overrides `relay.json` | Empty |
| `OPENFLARE_AGENT_TOKEN` | Node-specific Token; overrides `relay.json` | Empty |
| `OPENFLARE_DISCOVERY_TOKEN` | Auto-registration Token; overrides `relay.json` | Empty |
| `OPENFLARE_NODE_NAME` | Node name; overrides `relay.json` | Empty |
| `OPENFLARE_NODE_IP` | Node IP; overrides `relay.json` | Empty |
| `OPENFLARE_DATA_DIR` | Relay data directory; overrides `relay.json` | Empty |
| `OPENFLARE_FRPS_PATH` | frps binary path; overrides `relay.json` | Empty |
## Relay CLI Arguments
| Argument | Description | Default Value |
| --- | --- | --- |
| `-config` | Path to the Relay configuration file | `./relay.json` |
## Relay Configuration Fields
| Field | Description | Required | Default Value / Behavior |
### 2. Relational DB Config (`database:`)
| YAML path | Override env var | Description | Default |
| --- | --- | --- | --- |
| `server_url` | Control plane URL | Yes | None |
| `agent_token` | Node-specific access Token | Mutually exclusive with discovery_token | Empty |
| `discovery_token` | Global auto-registration Token | Mutually exclusive with agent_token | Empty |
| `node_name` | Node name | No | Hostname |
| `node_ip` | Relay listening IP for tunnel traffic | No | Auto-detect, prioritizes outbound public IP |
| `frps_path` | Path to the `frps` binary | No | `frps` (system PATH) |
| `data_dir` | Relay runtime data directory | No | `data` in the config folder |
| `state_path` | Path to store local state JSON file | No | `data_dir/relay-state.json` |
| `heartbeat_interval` | Heartbeat polling interval | No | `10000` milliseconds, supports Go duration strings |
| `request_timeout` | HTTP request timeout duration | No | `10000` milliseconds, supports Go duration strings |
| `database.enabled` | `DB_ENABLED` | enable PostgreSQL; `false` falls back to SQLite | `true` |
| `database.sqlite_path` | `SQLITE_PATH` | SQLite DB file path when PostgreSQL disabled | `openflare.db` |
| `database.host` | `DB_HOST` | PostgreSQL host | `127.0.0.1` |
| `database.port` | `DB_PORT` | PostgreSQL port | `5432` |
| `database.username` | `DB_USERNAME` | PostgreSQL username | `openflare` |
| `database.password` | `DB_PASSWORD` | PostgreSQL password | `replace-with-strong-password` |
| `database.database` | `DB_NAME` | PostgreSQL database name | `openflare` |
| `database.ssl_mode` | `DB_SSL_MODE` | PostgreSQL SSL mode | `disable` |
| `database.time_zone` | `DB_TIMEZONE` | DB session timezone | `UTC` |
| `database.log_level` | `DB_LOG_LEVEL` | GORM SQL log level (`info` / `warn` / `error` / `silent`) | `info` |
| `database.max_idle_conn` | `DB_MAX_IDLE_CONN` | connection pool max idle | `16` |
| `database.max_open_conn` | `DB_MAX_OPEN_CONN` | connection pool max open | `128` |
## OpenFlared (Client) Environment Variables
| Environment Variable | Description | Default Value |
| --- | --- | --- |
| `LOG_LEVEL` | Logging level for the client | `info` |
| `OPENFLARE_SERVER_URL` | Server URL; overrides `flared.json` | Empty |
| `OPENFLARE_TUNNEL_TOKEN` | Tunnel access Token; overrides `flared.json` | Empty |
| `OPENFLARE_DATA_DIR` | Client data directory; overrides `flared.json` | Empty |
| `OPENFLARE_FRPC_PATH` | frpc binary path; overrides `flared.json` | Empty |
## OpenFlared (Client) CLI Arguments
| Argument | Description | Default Value |
| --- | --- | --- |
| `-config` | Path to the client configuration file | `./flared.json` |
## OpenFlared (Client) Configuration Fields
| Field | Description | Required | Default Value / Behavior |
### 3. Redis Config (`redis:`)
| YAML path | Override env var | Description | Default |
| --- | --- | --- | --- |
| `server_url` | Control plane URL | Yes | None |
| `tunnel_token` | Tunnel dedicated access Token | Yes | None |
| `frpc_path` | Path to the `frpc` binary | No | `frpc` (system PATH) |
| `data_dir` | Client runtime data directory | No | `data` in the config folder |
| `state_path` | Path to store local state JSON file | No | `data_dir/flared-state.json` |
| `heartbeat_interval` | Heartbeat polling interval | No | `10000` milliseconds, supports Go duration strings |
| `sync_interval` | Configuration sync interval | No | `30000` milliseconds, supports Go duration strings |
| `request_timeout` | HTTP request timeout duration | No | `10000` milliseconds, supports Go duration strings |
| `redis.enabled` | `REDIS_ENABLED` | enable Redis. **The async queue and sync depend on it; must be on** | `true` |
| `redis.addrs` | `REDIS_ADDR` | Redis single/cluster address array (env var sets a single address) | `["127.0.0.1:6379"]` |
| `redis.username` | `REDIS_USERNAME` | Redis username (if any) | empty |
| `redis.password` | `REDIS_PASSWORD` | Redis password | empty |
| `redis.db` | `REDIS_DB` | Redis logical DB number | `0` |
| `redis.key_prefix` | `REDIS_KEY_PREFIX` | system key prefix in Redis | `openflare:` |
| `redis.pool_size` | `REDIS_POOL_SIZE` | Redis connection pool size | `100` |
| `redis.maint_notifications` | `REDIS_MAINT_NOTIFICATIONS` | enable Redis maintenance-notifications negotiation at startup; keep off when compatibility is unclear; restart needed after change | `false` |
## Common Configuration Combos
### 4. ClickHouse Config (`clickhouse:`)
### Production Server + PostgreSQL
> **Note**: these are OpenFlare **client** connection params. For ClickHouse **server** small-host tuning: curl `performance.xml` to `./config/clickhouse/`, then mount it as a single file at the container's `config.d/performance.xml` — see [Start the Server](../deployment/server.md).
```bash
export SESSION_SECRET='replace-with-a-long-random-string'
export DSN='postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable'
export GIN_MODE='release'
export LOG_LEVEL='info'
```
| YAML path | Override env var | Description | Default |
| --- | --- | --- | --- |
| `clickhouse.enabled` | `CLICKHOUSE_ENABLED` | enable ClickHouse. **Node metrics and access logs are written here at scale**. Off by default: missing config or `false` disables it and the primary DB handles logs/metrics; explicit `true` or setting `CLICKHOUSE_HOST` enables it | `false` |
| `clickhouse.hosts` | `CLICKHOUSE_HOST` | ClickHouse cluster address array (env var sets a single address) | `["127.0.0.1:9000"]` |
| `clickhouse.username` | `CLICKHOUSE_USERNAME` | ClickHouse username | `default` |
| `clickhouse.password` | `CLICKHOUSE_PASSWORD` | ClickHouse password | `replace-with-clickhouse-password` |
| `clickhouse.database` | `CLICKHOUSE_NAME` | ClickHouse database name | `openflare` |
| `clickhouse.max_idle_conn` | - | client idle connections (low by default for small hosts) | `8` |
| `clickhouse.max_open_conn` | - | client max open connections | `16` |
| `clickhouse.conn_max_lifetime` | - | connection max lifetime (seconds) | `3600` |
| `clickhouse.dial_timeout` | - | dial timeout (seconds) | `5` |
| `clickhouse.block_buffer_size` | - | native-protocol block buffer rows | `32` |
### Local Server + SQLite
### 5. System Log Config (`log:`)
| YAML path | Override env var | Description | Default |
| --- | --- | --- | --- |
| `log.level` | `LOG_LEVEL` | global log level (`debug` / `info` / `warn` / `error` / `fatal`) | `info` |
| `log.format` | `LOG_FORMAT` | log format (`console` readable / `json` structured) | `console` |
| `log.output` | `LOG_OUTPUT` | log output (`stdout` / `file`) | `stdout` |
| `log.file_path` | - | log file path when output is file | `./logs/app.log` |
| `log.max_size` | - | max single log file size (MB); auto-rotates beyond | `100` |
| `log.max_age` | - | max days to keep rotated log files | `30` |
```bash
export SESSION_SECRET='dev-session-secret'
export SQLITE_PATH='./openflare-dev.db'
export LOG_LEVEL='debug'
go run .
```
### 6. Async Task Worker Queue Config (`worker:`)
| YAML path | Override env var | Description | Default |
| --- | --- | --- | --- |
| `worker.concurrency` | `WORKER_CONCURRENCY` | max concurrent tasks consumed by the background Worker | `20` |
| `worker.strict_priority`| `WORKER_STRICT_PRIORITY` | strictly assign consumer threads by queue priority (else weighted round-robin) | `false` |
### Agent + Default OpenResty
### 7. Tracing OpenTelemetry Config (`otel:`)
| YAML path | Override env var | Description | Default |
| --- | --- | --- | --- |
| `otel.sampling_rate` | `OTEL_SAMPLING_RATE` | global OTel sampling rate. `0.0` no sampling, `1.0` full tracing | `0.0` |
| `otel.tracer_name` | `OTEL_TRACER_NAME` | global OTel tracer instance name | `github.com/Rain-kl/OpenFlare` |
```json
{
"server_url": "http://your-server:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "/opt/openflare-agent/data",
"openresty_path": "openresty",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
---
### Agent + Customized OpenResty Paths
## Runtime System Config (SystemConfig)
```json
{
"server_url": "http://your-server:3000",
"agent_token": "replace-with-node-auth-token",
"data_dir": "/var/lib/openflare-agent",
"openresty_path": "/usr/local/openresty/nginx/sbin/openresty",
"main_config_path": "/var/lib/openflare-agent/etc/nginx/nginx.conf",
"route_config_path": "/var/lib/openflare-agent/etc/nginx/conf.d/openflare_routes.conf",
"access_log_path": "/var/lib/openflare-agent/var/log/openflare/access.log",
"cert_dir": "/var/lib/openflare-agent/etc/nginx/certs",
"lua_dir": "/var/lib/openflare-agent/etc/nginx/lua",
"runtime_config_dir": "/var/lib/openflare-agent/etc/openflare",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
These items are stored in the `w_system_configs` table. Changes actively notify Redis cache invalidation for dynamic hot-update; admins manage them via the admin UI.
### Relay (Server-side) Default Configuration
### 1. Base & Business Runtime Config
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| `site_name` | `string` | admin platform display name | `OpenFlare` |
| `server_address` | `string` | public access address of the admin console, used to assemble OAuth callbacks and download links | empty |
| `password_login_enabled` | `bool` | allow admin login with normal username/password | `true` |
| `registration_enabled` | `bool` | allow self-service new-user registration (off by default; root invites or distributes) | `false` |
| `password_register_enabled` | `bool` | allow direct email/password registration on the frontend | `false` |
| `oidc_login_enabled` | `bool` | enable OIDC (SSO) third-party passwordless login | `true` |
| `max_api_keys_per_user` | `int` | max API keys (API Tokens) per admin user | `5` |
| `login_session_ttl_hours` | `int` | user session lifetime in the browser cookie (hours). 0 = clear on browser close | `0` |
| `upload_allowed_extensions` | `string` | allowed upload file extensions (comma-separated; empty = unlimited) | `jpg,png,webp` |
| `file_access_whitelist` | `json` | file business types allowed for public download/access without login (JSON array) | `["avatar"]` |
| `disk_cache_max_size_mb` | `int` | platform local disk cache max storage (MB) | `100` |
| `disk_cache_ttl_minutes` | `int` | local disk cache object default TTL (minutes) | `60` |
| `disk_cache_lru_enabled` | `bool` | use LRU eviction when local disk cache space is low | `true` |
| `update_upstream_repository` | `string` | GitHub repo for self-update detection | `Rain-kl/OpenFlare` |
| `storage_config` | `json` | object-storage structured config (JSON): local disk and AWS S3-compatible storage | local-storage mode |
| `relay_frps_web_ui_enabled` | `bool` | enable the embedded frps traffic-monitoring Web UI on relay nodes | `false` |
| `relay_frps_web_ui_port` | `int` | host port the relay frps monitoring panel listens on | `17500` |
| `search_engine_indexing_enabled` | `bool` | allow search engines to crawl/index the site | `false` |
| `menu_display_config` | `string` | menu display structured config (JSON string, format `{url: enabled}`) | `{}` |
| `pages_max_package_size_mb` | `int` | Pages deployment package upload size cap (MiB, range 1–2048) | `100` |
| `pages_max_history_count` | `int` | max historical deployments kept per Pages project (0 = unlimited) | `20` |
`relay.json`:
### 2. Human Verification (PoW Captcha)
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| `cap_login_enabled` | `bool` | require local PoW anti-brute-force human verification on the login page | `false` |
| `cap_auto_solve` | `bool` | auto-start background PoW computation on page load (no manual click) | `true` |
| `cap_challenge_count` | `int` | number of PoW challenges required. More = longer compute (recommended 1–5) | `1` |
| `cap_challenge_difficulty`| `int`| PoW hash prefix-match difficulty per challenge. Recommended 3-5 | `4` |
| `cap_challenge_size` | `int` | challenge salt length | `32` |
| `cap_challenge_ttl_seconds`| `int`| max valid time to submit the computed challenge (seconds); auto-invalidates on timeout | `600` |
| `cap_token_ttl_seconds` | `int` | validity of the login credential after solving (seconds); must log in within the window | `1200` |
```json
{
"server_url": "http://your-server:3000",
"agent_token": "replace-with-relay-auth-token",
"frps_path": "frps",
"data_dir": "/opt/openflare-relay/data",
"heartbeat_interval": 10000,
"request_timeout": 10000
}
```
### 3. SMTP Email Config
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| `smtp_host` | `string` | SMTP server address | empty |
| `smtp_port` | `int` | SMTP port (usually 465 SSL or 587 STARTTLS) | `465` |
| `smtp_username` | `string` | SMTP account email | empty |
| `smtp_password` | `string` | SMTP account auth password/cert key (encrypted on save, never echoed) | empty |
| `email_login_verification_enabled` | `bool` | send a one-time 6-digit code for second-factor auth on email login | `false` |
| `email_register_verification_enabled` | `bool` | force email verification with a registration code for self-service registration | `false` |
### OpenFlared (Client-side) Default Configuration
### 4. Node & Agent Ops Runtime
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| `agent_discovery_token` | `string` | global discovery Token for one-click first-time node registration | none (auto-generated on first visit) |
| `agent_heartbeat_interval`| `int` | standard heartbeat interval dispatched to all Agents (ms) | `3000` (3s) |
| `agent_websocket_upgrade_enabled` | `bool` | allow Agents to upgrade to a persistent WebSocket connection after HTTP heartbeat handshake | `true` |
| `node_offline_threshold` | `int` | no-response threshold (ms) after which a node is marked offline in the admin panel | `60000` (60s) |
| `agent_update_repo` | `string` | Release repo source for Agent self-binary updates | `Rain-kl/OpenFlare` |
| `geoip_provider` | `string` | GeoIP provider, e.g. `maxmind`, for WAF geo analysis | `ipinfo` |
`flared.json`:
### 5. Uptime Kuma Monitoring Sync
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| `uptime_kuma_enabled` | `bool` | auto-sync generated Uptime Kuma HTTP monitors after config release/activation | `false` |
| `uptime_kuma_url` | `string` | Uptime Kuma instance access URL (with port and path) | empty |
| `uptime_kuma_username` | `string` | Uptime Kuma admin username for sync API auth | empty |
| `uptime_kuma_password` | `string` | Uptime Kuma login password (encrypted on save, never echoed) | empty |
| `uptime_kuma_monitor_scope`| `string` | route scope for auto-generated monitors (`all` sites or `selected`) | `all` |
| `uptime_kuma_selected_sites`| `string` | selected proxied-site Site Name list to monitor (comma-separated) | empty |
| `uptime_kuma_sync_interval`| `int` | differential scan/calibration sync frequency to the Uptime Kuma instance (minutes) | `5` |
| `uptime_kuma_interval` | `int` | HTTP GET probe period of generated monitors (seconds) | `60` |
| `uptime_kuma_retry` | `int` | max reconnect retries after probe connection failures | `0` |
| `uptime_kuma_retry_interval`| `int` | pause between failed reconnect retries (seconds) | `60` |
| `uptime_kuma_timeout` | `int` | timeout for an HTTP GET monitor request (seconds) | `48` |
```json
{
"server_url": "http://your-server:3000",
"tunnel_token": "replace-with-tunnel-token",
"frpc_path": "frpc",
"data_dir": "/opt/openflared/data",
"heartbeat_interval": 10000,
"sync_interval": 30000,
"request_timeout": 10000
}
```
### 6. OpenResty Core Main Config & Rendering Options
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| `openresty_default_server_return_status` | `int` | status code returned for requests hitting no matching route by default | `421` |
| `openresty_worker_processes` | `string` | nginx `worker_processes`; fixed integer or `auto` | `auto` |
| `openresty_worker_connections` | `int` | nginx `worker_connections` per-process max connections | `4096` |
| `openresty_worker_rlimit_nofile` | `int` | nginx `worker_rlimit_nofile` max open file descriptors | `65535` |
| `openresty_events_use` | `string` | event polling engine (e.g. `epoll` preferred on Linux) | `epoll` |
| `openresty_events_multi_accept_enabled` | `bool` | accept all pending connection handshakes in one batch | `true` |
| `openresty_keepalive_timeout` | `int` | nginx `keepalive_timeout` (seconds) | `20` |
| `openresty_keepalive_requests` | `int` | max requests per reused TCP connection | `1000` |
| `openresty_client_header_timeout` | `int` | read timeout for the client Request Header (seconds) | `15` |
| `openresty_client_body_timeout` | `int` | read timeout for the client Request Body (seconds) | `15` |
| `openresty_client_max_body_size` | `string` | max client request Body size, with a unit like `10m`/`50m` | `64m` |
| `openresty_large_client_header_buffers` | `string` | buffers for oversized request headers (e.g. `4 16k`) | `4 16k` |
| `openresty_send_timeout` | `int` | max interval for sending Response data to the client (seconds) | `30` |
| `openresty_resolvers` | `string` | DNS resolver addresses/params for dynamic name resolution on nodes | empty |
| `openresty_proxy_connect_timeout` | `int` | TCP handshake timeout to the origin (seconds) | `3` |
| `openresty_proxy_send_timeout` | `int` | max interval for writing request data to the origin (seconds) | `60` |
| `openresty_proxy_read_timeout` | `int` | max wait for origin response data (seconds) | `60` |
| `openresty_websocket_enabled` | `bool` | auto-load WebSocket-supporting globals and headers in the HTTP section | `true` |
| `openresty_http3_enabled` | `bool` | render HTTP/3 QUIC dual-stack listen capability in generated nginx listens | `true` |
| `openresty_proxy_request_buffering_enabled`| `bool` | fully buffer the client Request Body before forwarding to the origin | `false` |
| `openresty_proxy_buffering_enabled` | `bool` | buffer large origin Response data before forwarding to the user | `true` |
| `openresty_proxy_buffers` | `string` | nginx proxy response buffer count/size (e.g. `16 16k`) | `16 16k` |
| `openresty_proxy_buffer_size` | `string` | buffer for origin Response Header | `8k` |
| `openresty_proxy_busy_buffers_size` | `string` | Busy-state buffer cap when response stream is oversized | `64k` |
| `openresty_gzip_enabled` | `bool` | enable gzip real-time compression on eligible content | `true` |
| `openresty_gzip_min_length` | `int` | file size threshold for gzip; below it, skip to save CPU | `1024` (1KB) |
| `openresty_gzip_comp_level` | `int` | gzip level 1-9; higher = more compression, more CPU | `5` |
| `openresty_cache_enabled` | `bool` | initialize the proxy cache region (Proxy Cache Path) in global config | `false` |
| `openresty_cache_path` | `string` | proxy cache temp physical dir on the node | `__OPENFLARE_PROXY_CACHE_PATH__` |
| `openresty_cache_levels` | `string` | proxy cache directory tree level layout | `1:2` |
| `openresty_cache_inactive` | `string` | time after which an unaccessed cache file is invalidated from disk | `30m` |
| `openresty_cache_max_size` | `string` | max disk quota for the proxy cache region on a node | `1g` |
| `openresty_cache_key_template` | `string` | default proxy cache key template | `$scheme$host$request_uri` |
| `openresty_cache_lock_enabled` | `bool` | queue/lock origin connections on high-concurrency cache misses for the same expired resource | `true` |
| `openresty_cache_lock_timeout` | `string` | max queue wait for the proxy cache lock | `5s` |
| `openresty_cache_use_stale` | `string` | serve stale cache on specific origin errors (500/502/504 etc.) | `error timeout updating http_500 http_502 http_503 http_504` |
| `openresty_default_limit_conn_per_server` | `int` | default concurrent-connection cap per server when a site has no config; `0` = off | `0` |
| `openresty_default_limit_conn_per_ip` | `int` | default per-IP concurrent cap when a site has no config; `0` = off | `0` |
| `openresty_default_limit_rate` | `string` | default per-request bandwidth when a site has no config (e.g. `512k`); empty = off | empty |
| `openresty_default_limit_req_per_ip` | `string` | default per-IP request rate limit when a site has no config (e.g. `10r/s`, `100r/m`); empty = off | empty |
| `openresty_main_config_template` | `string` | fully rewrite the OpenResty nginx.conf skeleton template | empty (built-in default skeleton) |
## Maintenance Rules
### 7. Origin Error Page
This document must be updated in sync when any of the following change:
Global origin error page config; written into the config version snapshot and distributed to edge Agents on release/rollback. Only affects **reverse proxy** routes; Pages static routes are unaffected. Admin entry:「Website Management → Error Page」. Design: [Origin Error Page Design](../design/origin-error-page.md).
* Server CLI arguments.
* Server environment variables.
* Agent CLI arguments and configuration parameters.
* Relay CLI arguments and configuration parameters.
* Client CLI arguments and configuration parameters.
* Default values, scopes, or examples of any configuration items.
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| `origin_error_page_enabled` | `bool` | enable the global origin error page. When on, matching status codes from origin/gateway are replaced by custom/default HTML with the **HTTP status kept**; when off, no directives are generated and pass-through resumes. Requires a config release to take effect | `true` |
| `origin_error_page_get_only` | `bool` | only apply to **GET** requests. When on, only matching GET error statuses return the custom error page; POST/PUT and other methods **pass through the origin response** (original status and body unchanged) | `false` |
| `origin_error_page_status_codes` | `json` | status code tag JSON array triggering the error page. Supports single codes (e.g. `522`) and closed ranges (e.g. `500-599`); single codes and range endpoints must be in **400–599**, with `lo ≤ hi`. The expanded result must not be empty when enabled | `["500-599"]` |
| `origin_error_page_html` | `string` | custom error page HTML. Empty uses the built-in OpenFlare default template (minimal white); supports `{{status}}` (matches the HTTP status) and `{{host}}` (request Host). Max **256 KiB** (bytes). Don't embed untrusted third-party scripts | empty |
---
### 8. Log Database
Runtime config after log-store decoupling: the log primary DB is managed by the「Switch Log Database」task (internal/protected keys, forbidden for manual admin edits); access-log retention days are set per storage DB in business config; performance metrics (CPU/memory/disk/network) decay fast and use a shared short retention independent of the access-log retention config.
| Key | Type | Description | Default |
| --- | --- | --- | --- |
| `log_database` | `string` | current log primary DB (`postgres` / `sqlite` / `clickhouse`). **Internal protected key**: only written by the「Switch Log Database」migration task; admins can't create/modify manually | follows the primary DB (`postgres` when PostgreSQL enabled, else `sqlite`; `clickhouse` preferred when ClickHouse enabled) |
| `log_db_migration` | `string` | log migration freeze marker (`migrating` or empty). **Internal protected key**: only the migration task writes it; while set, log writes return 503「log DB migrating, not writable」 | empty |
| `log_retention_days_postgres` | `int` | access-log retention days in the PostgreSQL log DB (expired logs deleted by the daily garbage-collection task) | `30` |
| `log_retention_days_sqlite` | `int` | access-log retention days in the SQLite log DB | `30` |
| `log_retention_days_clickhouse` | `int` | access-log retention days in the ClickHouse log DB | `30` |
| `metric_retention_days` | `int` | performance metric (CPU/memory/disk/network) retention days; shared short retention across the three DBs (independent of access-log retention) | `3` |
---
## Frontend Build Env Vars
| Env var | Purpose | Default |
| --- | --- | --- |
| `WAVELET_BACKEND_URL` | backend address for server-side rendering and dev proxy | `http://localhost:3000` |
| `NEXT_PUBLIC_WAVELET_BACKEND_URL` | backend address for browser API requests; empty = same-origin | empty |
| `NEXT_PUBLIC_APP_VERSION` | displayed frontend version | `dev` |
---
## Agent Env Vars
| Env var | Purpose | Default |
| --- | --- | --- |
| `LOG_LEVEL` | Agent log level | `info` |
| `OPENFLARE_SERVER_URL` | control-plane address, overrides `agent.json` | empty |
| `OPENFLARE_AGENT_TOKEN` | node-specific auth Token, overrides `agent.json` | empty |
| `OPENFLARE_DISCOVERY_TOKEN` | first-time auto-registration Token, overrides `agent.json` | empty |
| `OPENFLARE_NODE_NAME` | node name, overrides `agent.json` | empty |
| `OPENFLARE_NODE_IP` | node IP, overrides `agent.json` | empty |
| `OPENFLARE_DATA_DIR` | Agent data dir, overrides `agent.json` | empty |
| `OPENFLARE_OPENRESTY_PATH` | OpenResty binary path, overrides `agent.json` | empty |
| `OPENFLARE_PAGES_DIR` | Pages static deployment dir, overrides `agent.json` | empty |
| `OPENFLARE_HEARTBEAT_INTERVAL` | heartbeat interval, overrides `agent.json` | empty |
| `OPENFLARE_REQUEST_TIMEOUT` | request timeout, overrides `agent.json` | empty |
| `OPENFLARE_OPENRESTY_OBSERVABILITY_PORT` | local observability port, overrides `agent.json` | empty |
| `OPENFLARE_MMDB_PATH` | WAF GeoIP mmdb path, overrides `agent.json` | empty |
| `OPENFLARE_MMDB_UPDATE_INTERVAL` | WAF GeoIP mmdb update interval, overrides `agent.json` | empty |
| `OPENFLARE_MMDB_DOWNLOAD_URL` | WAF GeoIP mmdb download URL, overrides `agent.json` | empty |
| `OPENFLARE_CITY_MMDB_PATH` | WAF City MMDB path, overrides `agent.json` | empty |
| `OPENFLARE_CITY_MMDB_DOWNLOAD_URL` | WAF City MMDB download URL, overrides `agent.json` | empty |
---
## Agent CLI Args and Config Fields
### CLI Args
- `-config`: Agent config file path, default `./agent.json`.
### Config File Fields (agent.json)
| Field | Purpose | Required | Default/Behavior |
| --- | --- | --- | --- |
| `server_url` | control-plane address | yes | none |
| `agent_token` | node-specific auth Token | one of two with `discovery_token` | empty |
| `discovery_token` | global Token for first-time auto-registration | one of two with `agent_token` | empty |
| `node_name` | node name | no | hostname automatically |
| `node_ip` | node IP | no | auto-detected, prefers the public egress IP; falls back to local NIC detection on failure |
| `openresty_path` | OpenResty binary path | no | `openresty` |
| `openresty_observability_port` | local observability & OpenResty health-check port | no | `18081` |
| `data_dir` | Agent data dir | no | `data` next to the config file |
| `main_config_path` | OpenResty main config write path | no | `data_dir/etc/nginx/nginx.conf` |
| `route_config_path` | route config write path | no | `data_dir/etc/nginx/conf.d/openflare_routes.conf` |
| `access_log_path` | OpenResty access log path | no | `data_dir/var/log/openflare/access.log` |
| `cert_dir` | certificate write dir | no | `data_dir/etc/nginx/certs` |
| `openresty_cert_dir` | certificate dir read by the OpenResty config | no | same as `cert_dir` |
| `lua_dir` | Lua scripts & static assets write dir | no | `data_dir/etc/nginx/lua` |
| `openresty_lua_dir` | Lua dir read by the OpenResty config | no | same as `lua_dir` |
| `runtime_config_dir` | Agent runtime config write dir, e.g. `pow_config.json` | no | `data_dir/etc/openflare` |
| `pages_dir` | Pages deployment package extraction & current deploy dir | no | `data_dir/var/lib/openflare/pages` |
| `mmdb_path` | WAF GeoIP mmdb file path | no | `data_dir/etc/openflare/GeoLite2-Country.mmdb` |
| `city_mmdb_path` | WAF City MMDB file path | no | `data_dir/etc/openflare/GeoLite2-City.mmdb` |
| `mmdb_update_interval` | WAF GeoIP mmdb update interval | no | `86400000` ms (24h) |
| `mmdb_download_url` | WAF GeoIP mmdb periodic update URL | no | GeoLite2 Country update URL; first download when the disk file is missing (Docker images COPY default-path files) |
| `city_mmdb_download_url` | WAF City MMDB periodic update URL | no | GeoLite2 City update URL; first download when the disk file is missing (Docker images COPY default-path files) |
| `observability_buffer_path` | observability backfill buffer file path | no | `data_dir/var/lib/openflare/observability-buffer.json` |
| `observability_replay_minutes` | auto-backfill recent observability window (minutes) | no | `60` |
| `state_path` | Agent local state file path | no | `data_dir/var/lib/openflare/agent-state.json` |
| `heartbeat_interval` | heartbeat interval | no | `3000` ms |
| `request_timeout` | HTTP request timeout | no | `10000` ms |
---
## Relay Env Vars and Config Fields
### Env Vars
- `LOG_LEVEL`: Relay log level, default `info`.
- Supports `OPENFLARE_SERVER_URL`, `OPENFLARE_AGENT_TOKEN`, `OPENFLARE_DISCOVERY_TOKEN`, `OPENFLARE_NODE_NAME`, `OPENFLARE_NODE_IP`, `OPENFLARE_DATA_DIR`, `OPENFLARE_FRPS_PATH` overrides.
### CLI Args
- `-config`: Relay config file path, default `./relay.json`.
### Config File Fields (relay.json)
| Field | Purpose | Required | Default/Behavior |
| --- | --- | --- | --- |
| `server_url` | control-plane address | yes | none |
| `agent_token` | relay node-specific auth Token | one of two with `discovery_token` | empty |
| `discovery_token` | global Token for first-time auto-registration | one of two with `agent_token` | empty |
| `node_name` | node name | no | hostname automatically |
| `node_ip` | relay node IP for receiving tunnel traffic | no | auto-detected, prefers the public egress IP; falls back to NIC detection on failure |
| `frps_path` | frps binary path | no | `frps` (found on system PATH) |
| `data_dir` | Relay runtime data dir | no | `data` next to the config file |
| `state_path` | Relay local state file path | no | `data_dir/relay-state.json` |
| `heartbeat_interval` | heartbeat interval | no | `10000` ms |
| `request_timeout` | HTTP request timeout | no | `10000` ms |
---
## OpenFlared (Client) Env Vars and Config Fields
### Env Vars
- `LOG_LEVEL`: Client log level, default `info`.
- Supports `OPENFLARE_SERVER_URL`, `OPENFLARE_TUNNEL_TOKEN`, `OPENFLARE_DATA_DIR`, `OPENFLARE_FRPC_PATH` overrides.
### CLI Args
- `-config`: Client config file path, default `./flared.json`.
### Config File Fields (flared.json)
| Field | Purpose | Required | Default/Behavior |
| --- | --- | --- | --- |
| `server_url` | control-plane address | yes | none |
| `tunnel_token` | tunnel-specific auth Token | yes | none |
| `frpc_path` | frpc binary path | no | `frpc` (found on system PATH) |
| `data_dir` | Client runtime data dir | no | `data` next to the config file |
| `state_path` | Client local state file path | no | `data_dir/flared-state.json` |
| `heartbeat_interval` | heartbeat interval | no | `10000` ms |
| `sync_interval` | config pull/sync interval | no | `30000` ms |
| `request_timeout` | HTTP request timeout | no | `10000` ms |
+6 -8
View File
@@ -1,13 +1,11 @@
# Reference Manuals
# Reference
You will learn: Which information belongs to stable reference manuals, and where to look up configurations, commands, APIs, and repository structures.
You will learn: which information counts as stable reference material, and where to look up config, commands, API, and repository structure.
This section collects stable information at the runtime, API, and repository layers, suitable for rapid lookup during deployment, integration, and troubleshooting.
This section consolidates stable runtime, interface, and repository-level information for quick reference during deployment, integration, and troubleshooting.
| Page | Content |
| --- | --- |
| [Configuration Options](./configuration.md) | Server environment variables, CLI arguments, runtime Options, and Agent configuration parameters |
| [CLI Commands](./cli.md) | Common CLI commands for starting, building, testing, installing, and uninstalling |
| [API Conventions](./api.md) | Response structures, authentication, and routing paths for Admin and Agent APIs |
| [Repository Structure](../design/repository.md) | Scope of responsibilities and folder layering of the Server, Agent, Relay, and Client |
| [Deployment & Upgrade](../deployment/) | Server and Agent deployment, configuration, and upgrade guides (dedicated section) |
| [Configuration](./configuration.md) | Server env vars, CLI args, runtime Options, and Agent config fields |
| [Commands & Scripts](./cli.md) | common start, build, test, install, and uninstall commands |
| [Repository Structure](../design/index.md#repository-structure) | monorepo directory responsibilities and layering (`main.go`, `cmd/`, `internal/apps/`, `frontend/`, etc.) |