diff --git a/docker-compose.yml b/docker-compose.yml index 3625e6f..9c024ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/docs-site/docs/deployment/bare-metal.md b/docs-site/docs/deployment/bare-metal.md index 832c714..2200f56 100644 --- a/docs-site/docs/deployment/bare-metal.md +++ b/docs-site/docs/deployment/bare-metal.md @@ -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 diff --git a/docs-site/docs/deployment/configuration.md b/docs-site/docs/deployment/configuration.md index b03484b..1bcab27 100644 --- a/docs-site/docs/deployment/configuration.md +++ b/docs-site/docs/deployment/configuration.md @@ -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. diff --git a/docs-site/docs/deployment/docker.md b/docs-site/docs/deployment/docker.md index e516a3b..fd2a2de 100644 --- a/docs-site/docs/deployment/docker.md +++ b/docs-site/docs/deployment/docker.md @@ -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: diff --git a/docs-site/docs/features/multi-node.md b/docs-site/docs/features/multi-node.md index 32187a0..0bd6fdb 100644 --- a/docs-site/docs/features/multi-node.md +++ b/docs-site/docs/features/multi-node.md @@ -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. diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md index e1fbf70..60e49ba 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/bare-metal.md @@ -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 diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/configuration.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/configuration.md index 3be3200..7b35326 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/configuration.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/configuration.md @@ -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 可访问地址时,才建议留空。 diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/docker.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/docker.md index 435071d..0e9a9b4 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/docker.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/deployment/docker.md @@ -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_` 前缀环境变量覆盖: diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md index bfa3902..22804cf 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/multi-node.md @@ -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 控制台 → **节点管理** → **添加节点**,打开三步向导: diff --git a/server/config.example.yaml b/server/config.example.yaml index eebdc75..181482b 100644 --- a/server/config.example.yaml +++ b/server/config.example.yaml @@ -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 数据库路径 diff --git a/server/internal/app/app.go b/server/internal/app/app.go index 3aaa838..8b90960 100644 --- a/server/internal/app/app.go +++ b/server/internal/app/app.go @@ -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, }) diff --git a/server/internal/config/config.go b/server/internal/config/config.go index 5e9fecd..b5edb4c 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -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") diff --git a/server/internal/config/config_test.go b/server/internal/config/config_test.go index bf40354..7fe7d7e 100644 --- a/server/internal/config/config_test.go +++ b/server/internal/config/config_test.go @@ -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) + } +}