fix(cluster): support external master URL

- add server.external_url / BACKUPX_SERVER_EXTERNAL_URL for Agent install URL generation
- pass the configured external Master URL into install script and compose rendering
- document cluster deployment requirements for Docker, bare-metal, and multi-node setups

Fixes #55
This commit is contained in:
Wu Qing
2026-05-09 07:41:51 +08:00
committed by GitHub
parent f6bd185b9f
commit 61709dd4c9
13 changed files with 152 additions and 7 deletions

View File

@@ -22,6 +22,8 @@ services:
# - /home/user/data:/mnt/data:ro
environment:
- TZ=Asia/Shanghai
# 远程 Agent 需要通过公网或可路由地址连接 Master 时,取消注释并改成真实 URL
# - BACKUPX_SERVER_EXTERNAL_URL=https://backup.example.com
# 通过 BACKUPX_ 前缀环境变量覆盖配置:
# - BACKUPX_LOG_LEVEL=debug
# - BACKUPX_BACKUP_MAX_CONCURRENT=4

View File

@@ -25,6 +25,19 @@ The installer performs these steps automatically:
4. Installs `backupx.service` (systemd), enabled at boot
5. (Optional) installs an Nginx site file — see [Nginx Reverse Proxy](./nginx)
For multi-node clusters, edit `/etc/backupx/config.yaml` after installation and set the Master URL that remote Agents can reach:
```yaml
server:
external_url: "https://backup.example.com"
```
Restart BackupX after changing it:
```bash
sudo systemctl restart backupx
```
## From source
```bash

View File

@@ -15,13 +15,14 @@ server:
host: "0.0.0.0" # BACKUPX_SERVER_HOST
port: 8340 # BACKUPX_SERVER_PORT
mode: "release" # release | debug
external_url: "" # BACKUPX_SERVER_EXTERNAL_URL — public Master URL for Agent install scripts
database:
path: "./data/backupx.db" # BACKUPX_DATABASE_PATH — embedded SQLite
security:
jwt_secret: "" # BACKUPX_SECURITY_JWT_SECRET — auto-generated if empty
jwt_expires_in: "24h"
jwt_expire: "24h" # BACKUPX_SECURITY_JWT_EXPIRE
encryption_key: "" # AES-256-GCM key for storage config encryption
backup:
@@ -46,7 +47,20 @@ The environment wins when both file and env are set. All dot-paths become unders
| Config key | Env variable |
|------------|--------------|
| `server.port` | `BACKUPX_SERVER_PORT` |
| `server.external_url` | `BACKUPX_SERVER_EXTERNAL_URL` |
| `security.jwt_expire` | `BACKUPX_SECURITY_JWT_EXPIRE` |
| `log.level` | `BACKUPX_LOG_LEVEL` |
| `backup.max_concurrent` | `BACKUPX_BACKUP_MAX_CONCURRENT` |
| `backup.temp_dir` | `BACKUPX_BACKUP_TEMP_DIR` |
| `backup.bandwidth_limit` | `BACKUPX_BACKUP_BANDWIDTH_LIMIT` |
## Master external URL
Set `server.external_url` when BackupX is behind Docker, Nginx, a load balancer, or any reverse proxy whose internal Host is not reachable by remote Agents:
```yaml
server:
external_url: "https://backup.example.com"
```
This value is used when BackupX renders one-click Agent install scripts and docker-compose snippets. It must be reachable from every Agent host. Leave it empty only when `X-Forwarded-Proto` / `X-Forwarded-Host` are reliable and point to the same URL that Agents can access.

View File

@@ -25,6 +25,8 @@ services:
- /etc/nginx:/mnt/nginx-conf:ro
environment:
- TZ=Asia/Shanghai
# Required when remote Agents must connect through a public or routed URL:
# - BACKUPX_SERVER_EXTERNAL_URL=https://backup.example.com
- BACKUPX_LOG_LEVEL=info
- BACKUPX_BACKUP_MAX_CONCURRENT=2
@@ -42,6 +44,17 @@ docker compose up -d
To back up files from the host, mount them into the container. When creating a file-type task in the web UI, point the source path at the mount location (e.g. `/mnt/www`). Make sure the directory is visible inside the container.
## Multi-node clusters
When deploying Agents on other machines, set `BACKUPX_SERVER_EXTERNAL_URL` on the Master container to the URL that those Agents can reach:
```yaml
environment:
- BACKUPX_SERVER_EXTERNAL_URL=https://backup.example.com
```
Use an HTTPS URL if Agents cross untrusted networks. The generated one-click install scripts and docker-compose snippets use this value as `BACKUPX_AGENT_MASTER`.
## Environment variables
All configuration keys can be overridden with the `BACKUPX_` prefix:

View File

@@ -28,6 +28,19 @@ BackupX supports Master-Agent mode: backup tasks can be routed to specific nodes
## Walkthrough
### 0. Set the Master URL for production clusters
Before generating Agent install commands, make sure the Master URL shown to Agents is stable and reachable from every target host.
If BackupX runs behind Docker, Nginx, a load balancer, or an outer reverse proxy, configure `server.external_url` or `BACKUPX_SERVER_EXTERNAL_URL` on the Master:
```yaml title="config.yaml"
server:
external_url: "https://backup.example.com"
```
This URL is baked into systemd units, foreground commands, and docker-compose snippets. If it is wrong, Agents will install successfully but stay offline because they keep polling an internal or browser-only address.
### 1. Open the install wizard
In the Web Console → **Node Management** → **Add Node**. You'll see a three-step wizard.

View File

@@ -25,6 +25,19 @@ sudo ./install.sh
4. 安装并启用 `backupx.service` systemd 单元
5. (可选)生成 Nginx 站点配置 — 参见 [Nginx 反向代理](./nginx)
如果要部署多节点集群,安装后请编辑 `/etc/backupx/config.yaml`,设置远程 Agent 可访问到的 Master URL
```yaml
server:
external_url: "https://backup.example.com"
```
修改后重启 BackupX
```bash
sudo systemctl restart backupx
```
## 从源码构建
```bash

View File

@@ -15,13 +15,14 @@ server:
host: "0.0.0.0" # BACKUPX_SERVER_HOST
port: 8340 # BACKUPX_SERVER_PORT
mode: "release" # release | debug
external_url: "" # BACKUPX_SERVER_EXTERNAL_URL — Agent 安装脚本使用的 Master 对外 URL
database:
path: "./data/backupx.db" # BACKUPX_DATABASE_PATH — 内嵌 SQLite
security:
jwt_secret: "" # BACKUPX_SECURITY_JWT_SECRET — 留空自动生成
jwt_expires_in: "24h"
jwt_expire: "24h" # BACKUPX_SECURITY_JWT_EXPIRE
encryption_key: "" # 用于加密存储配置的 AES-256-GCM 密钥
backup:
@@ -46,7 +47,20 @@ log:
| 配置项 | 环境变量 |
|--------|----------|
| `server.port` | `BACKUPX_SERVER_PORT` |
| `server.external_url` | `BACKUPX_SERVER_EXTERNAL_URL` |
| `security.jwt_expire` | `BACKUPX_SECURITY_JWT_EXPIRE` |
| `log.level` | `BACKUPX_LOG_LEVEL` |
| `backup.max_concurrent` | `BACKUPX_BACKUP_MAX_CONCURRENT` |
| `backup.temp_dir` | `BACKUPX_BACKUP_TEMP_DIR` |
| `backup.bandwidth_limit` | `BACKUPX_BACKUP_BANDWIDTH_LIMIT` |
## Master 对外 URL
当 BackupX 部署在 Docker、Nginx、负载均衡或多层反向代理后面且后端收到的内部 Host 不是远程 Agent 可访问地址时,请配置 `server.external_url`
```yaml
server:
external_url: "https://backup.example.com"
```
BackupX 会用这个地址渲染一键 Agent 安装脚本和 docker-compose 片段。该地址必须能被所有 Agent 主机访问。只有在 `X-Forwarded-Proto` / `X-Forwarded-Host` 可靠且正好指向 Agent 可访问地址时,才建议留空。

View File

@@ -25,6 +25,8 @@ services:
- /etc/nginx:/mnt/nginx-conf:ro
environment:
- TZ=Asia/Shanghai
# 远程 Agent 需要通过公网或可路由地址连接 Master 时必须配置:
# - BACKUPX_SERVER_EXTERNAL_URL=https://backup.example.com
- BACKUPX_LOG_LEVEL=info
- BACKUPX_BACKUP_MAX_CONCURRENT=2
@@ -42,6 +44,17 @@ docker compose up -d
想备份宿主机上的文件,需要将对应路径挂载进容器。在 Web UI 创建文件类型任务时,把源路径指向挂载后的容器内路径(如 `/mnt/www`)。
## 多节点集群
如果要在其他机器部署 Agent请在 Master 容器上设置 `BACKUPX_SERVER_EXTERNAL_URL`,值为所有 Agent 都能访问到的 URL
```yaml
environment:
- BACKUPX_SERVER_EXTERNAL_URL=https://backup.example.com
```
Agent 跨不可信网络访问时建议使用 HTTPS。控制台生成的一键安装脚本和 docker-compose 片段会把这个值写成 `BACKUPX_AGENT_MASTER`。
## 环境变量
所有配置项都可以通过 `BACKUPX_` 前缀环境变量覆盖:

View File

@@ -28,6 +28,19 @@ BackupX 支持 Master-Agent 模式:备份任务可以指定在哪个节点执
## 一键部署步骤
### 0. 为生产集群设置 Master 对外 URL
生成 Agent 安装命令前,请先确认 Master URL 对所有目标主机稳定可达。
如果 BackupX 部署在 Docker、Nginx、负载均衡或外层反向代理后面请在 Master 配置 `server.external_url` 或环境变量 `BACKUPX_SERVER_EXTERNAL_URL`
```yaml title="config.yaml"
server:
external_url: "https://backup.example.com"
```
该 URL 会写入 systemd 单元、前台运行命令和 docker-compose 片段。如果地址不正确Agent 可能安装成功但始终离线,因为它会持续轮询一个内网地址或仅浏览器可访问的地址。
### 1. 打开安装向导
Web 控制台 → **节点管理** → **添加节点**,打开三步向导:

View File

@@ -3,6 +3,7 @@ server:
host: "0.0.0.0"
port: 8340
mode: "release" # debug | release
external_url: "" # 可选Master 对 Agent 可达的 URL例如 https://backup.example.com
database:
path: "./data/backupx.db" # SQLite 数据库路径

View File

@@ -276,7 +276,7 @@ func New(ctx context.Context, cfg config.Config, version string) (*Application,
UserRepository: userRepo,
SystemConfigRepo: systemConfigRepo,
InstallTokenService: installTokenService,
MasterExternalURL: "", // 如需覆盖 URL可扩展 cfg.Server 增字段;目前留空依赖 X-Forwarded-* / Request.Host
MasterExternalURL: cfg.Server.ExternalURL,
DB: db,
Metrics: appMetrics,
})

View File

@@ -17,9 +17,10 @@ type Config struct {
}
type ServerConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Mode string `mapstructure:"mode"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Mode string `mapstructure:"mode"`
ExternalURL string `mapstructure:"external_url"`
}
type DatabaseConfig struct {
@@ -136,6 +137,7 @@ func applyDefaults(v *viper.Viper) {
v.SetDefault("server.host", "0.0.0.0")
v.SetDefault("server.port", 8340)
v.SetDefault("server.mode", "release")
v.SetDefault("server.external_url", "")
v.SetDefault("database.path", "./data/backupx.db")
v.SetDefault("security.jwt_expire", "24h")
v.SetDefault("backup.temp_dir", "/tmp/backupx")

View File

@@ -1,6 +1,10 @@
package config
import "testing"
import (
"os"
"path/filepath"
"testing"
)
func TestLoadUsesDefaultsWithoutConfigFile(t *testing.T) {
cfg, err := Load("")
@@ -18,3 +22,33 @@ func TestLoadUsesDefaultsWithoutConfigFile(t *testing.T) {
t.Fatalf("expected default database path, got %s", cfg.Database.Path)
}
}
func TestLoadReadsServerExternalURLFromFile(t *testing.T) {
configPath := filepath.Join(t.TempDir(), "config.yaml")
content := []byte("server:\n external_url: \"https://backup.example.com\"\n")
if err := os.WriteFile(configPath, content, 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
cfg, err := Load(configPath)
if err != nil {
t.Fatalf("Load returned error: %v", err)
}
if cfg.Server.ExternalURL != "https://backup.example.com" {
t.Fatalf("expected external URL from config, got %q", cfg.Server.ExternalURL)
}
}
func TestLoadReadsServerExternalURLFromEnv(t *testing.T) {
t.Setenv("BACKUPX_SERVER_EXTERNAL_URL", "https://env-backup.example.com")
cfg, err := Load("")
if err != nil {
t.Fatalf("Load returned error: %v", err)
}
if cfg.Server.ExternalURL != "https://env-backup.example.com" {
t.Fatalf("expected external URL from env, got %q", cfg.Server.ExternalURL)
}
}