diff --git a/README.md b/README.md index 5db127d..3e391ab 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,15 @@ docker run -d --name backupx -p 8340:8340 -v backupx-data:/app/data awuqing/back # Or prebuilt archive curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz tar xzf backupx-*.tar.gz && cd backupx-* && sudo ./install.sh + +# Or build and install on bare metal without Docker +git clone https://github.com/Awuqing/BackupX.git && cd BackupX +make build && sudo ./deploy/install.sh ``` For ARM64 hosts, use `backupx-linux-arm64.tar.gz`. The archive contains `backupx`, `web/`, `config.example.yaml`, and `install.sh`; run `install.sh` from the extracted directory. -Open `http://your-server:8340`, create the admin account, then follow the [5-minute Quick Start](https://awuqing.github.io/BackupX/docs/getting-started/quick-start). +Open `http://your-server:8340`, choose English or Chinese on the setup screen, create the first administrator account, then follow the [5-minute Quick Start](https://awuqing.github.io/BackupX/docs/getting-started/quick-start). ## Documentation diff --git a/README.zh-CN.md b/README.zh-CN.md index 661a9b0..e6a0b27 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -60,11 +60,15 @@ docker run -d --name backupx -p 8340:8340 -v backupx-data:/app/data awuqing/back # 或使用预编译包 curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz tar xzf backupx-*.tar.gz && cd backupx-* && sudo ./install.sh + +# 或从源码构建并裸机安装(无需 Docker) +git clone https://github.com/Awuqing/BackupX.git && cd BackupX +make build && sudo ./deploy/install.sh ``` ARM64 主机请下载 `backupx-linux-arm64.tar.gz`。预编译包内包含 `backupx`、`web/`、`config.example.yaml` 和 `install.sh`,请在解压后的目录内执行 `install.sh`。 -打开 `http://your-server:8340`,创建管理员账户,按 [5 分钟快速开始](https://awuqing.github.io/BackupX/zh-Hans/docs/getting-started/quick-start) 完成首次备份。 +打开 `http://your-server:8340`,在初始化页选择中文或 English 并创建首个管理员账户,按 [5 分钟快速开始](https://awuqing.github.io/BackupX/zh-Hans/docs/getting-started/quick-start) 完成首次备份。 ## 文档 diff --git a/deploy/install.sh b/deploy/install.sh index d451ae7..da068a1 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -14,7 +14,13 @@ if [ -f "$SCRIPT_DIR/backupx" ] && [ -d "$SCRIPT_DIR/web" ]; then CONFIG_TEMPLATE="${CONFIG_TEMPLATE:-$SCRIPT_DIR/config.example.yaml}" NGINX_SOURCE="${NGINX_SOURCE:-$SCRIPT_DIR/nginx.conf}" else - BIN_SOURCE="${BIN_SOURCE:-$PROJECT_ROOT/server/backupx}" + SOURCE_BIN_DEFAULT="$PROJECT_ROOT/server/bin/backupx" + # Keep compatibility with contributors who built the historical path by + # hand, while matching the canonical `make build` output first. + if [ ! -f "$SOURCE_BIN_DEFAULT" ] && [ -f "$PROJECT_ROOT/server/backupx" ]; then + SOURCE_BIN_DEFAULT="$PROJECT_ROOT/server/backupx" + fi + BIN_SOURCE="${BIN_SOURCE:-$SOURCE_BIN_DEFAULT}" WEB_SOURCE="${WEB_SOURCE:-$PROJECT_ROOT/web/dist}" CONFIG_TEMPLATE="${CONFIG_TEMPLATE:-$PROJECT_ROOT/server/config.example.yaml}" NGINX_SOURCE="${NGINX_SOURCE:-$PROJECT_ROOT/deploy/nginx.conf}" @@ -27,8 +33,9 @@ if [ "$(id -u)" -ne 0 ]; then fi if [ ! -f "$BIN_SOURCE" ]; then - echo "未找到后端二进制:$BIN_SOURCE" >&2 - echo "源码树安装请先执行:cd \"$PROJECT_ROOT/server\" && go build -o backupx ./cmd/backupx" >&2 + echo "Backend binary not found / 未找到后端二进制:$BIN_SOURCE" >&2 + echo "源码树安装请先在仓库根目录执行 make build(产物:server/bin/backupx)。" >&2 + echo "For a source install, run 'make build' in the repository root first." >&2 echo "发布包安装请确认当前目录包含 ./backupx、./web 和 ./install.sh。" >&2 exit 1 fi @@ -92,6 +99,41 @@ fi systemctl daemon-reload systemctl enable --now "$SERVICE_NAME" +# systemctl may return before the process has opened its HTTP listener. Verify +# the same unauthenticated endpoint used by the first-administrator screen so a +# broken bare-metal install cannot print a false success message. +HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:8340/api/auth/setup/status}" +READY=0 +ATTEMPT=1 +while [ "$ATTEMPT" -le 30 ]; do + if systemctl is-active --quiet "$SERVICE_NAME"; then + if command -v curl >/dev/null 2>&1; then + if curl -fsS --max-time 2 "$HEALTH_URL" >/dev/null 2>&1; then + READY=1 + break + fi + elif command -v wget >/dev/null 2>&1; then + if wget -q -T 2 -O /dev/null "$HEALTH_URL"; then + READY=1 + break + fi + else + echo "Warning / 警告:未找到 curl 或 wget,仅验证 systemd 服务状态。" >&2 + READY=1 + break + fi + fi + ATTEMPT=$((ATTEMPT + 1)) + sleep 1 +done + +if [ "$READY" -ne 1 ]; then + echo "BackupX did not become ready at $HEALTH_URL / 服务未通过就绪检查。" >&2 + systemctl status "$SERVICE_NAME" --no-pager >&2 || true + journalctl -u "$SERVICE_NAME" -n 50 --no-pager >&2 || true + exit 1 +fi + if [ -d "/etc/nginx/conf.d" ] && [ -f "$NGINX_SOURCE" ]; then install -m 0644 "$NGINX_SOURCE" "/etc/nginx/conf.d/$SERVICE_NAME.conf" if command -v nginx >/dev/null 2>&1; then @@ -111,6 +153,11 @@ cat <:8340 +首次访问 / First sign-in: + 1. 打开上面的地址,并可在登录页右上角选择 中文 或 English。 + 2. 页面显示“系统初始化 / System setup”时,创建首个管理员用户名和密码。 + 3. 如果未显示初始化表单,请先检查:$HEALTH_URL + (如已安装 nginx,脚本会自动写入反向代理配置,可继续用 80 端口访问。) 排查:若服务未监听端口,请查看日志: diff --git a/docs-site/docs/deployment/bare-metal.md b/docs-site/docs/deployment/bare-metal.md index 2200f56..ee81c82 100644 --- a/docs-site/docs/deployment/bare-metal.md +++ b/docs-site/docs/deployment/bare-metal.md @@ -10,20 +10,21 @@ description: systemd + Nginx deployment from the prebuilt release tarball or sou ```bash # Download the matching tarball -curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-v1.6.0-linux-amd64.tar.gz +curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz # Extract and install -tar xzf backupx-v*-linux-amd64.tar.gz && cd backupx-* +tar xzf backupx-linux-amd64.tar.gz && cd backupx-*-linux-amd64 sudo ./install.sh ``` The installer performs these steps automatically: 1. Creates a system user `backupx` -2. Copies the binary to `/opt/backupx/` -3. Generates a default `config.yaml` with safe JWT/encryption secrets +2. Copies the binary to `/opt/backupx/bin/backupx` and the web console to `/opt/backupx/web` +3. Installs the default configuration at `/etc/backupx/config.yaml` 4. Installs `backupx.service` (systemd), enabled at boot 5. (Optional) installs an Nginx site file — see [Nginx Reverse Proxy](./nginx) +6. Verifies the first-setup API before reporting success For multi-node clusters, edit `/etc/backupx/config.yaml` after installation and set the Master URL that remote Agents can reach: @@ -63,11 +64,13 @@ After=network.target [Service] Type=simple User=backupx +Group=backupx WorkingDirectory=/opt/backupx -ExecStart=/opt/backupx/backupx --config /opt/backupx/config.yaml +ExecStart=/opt/backupx/bin/backupx -config /etc/backupx/config.yaml Restart=on-failure -RestartSec=5s -LimitNOFILE=65536 +RestartSec=5 +NoNewPrivileges=true +LimitNOFILE=65535 [Install] WantedBy=multi-user.target @@ -79,17 +82,20 @@ Typical operations: sudo systemctl status backupx sudo journalctl -u backupx -f # live logs sudo systemctl restart backupx +curl -fsS http://127.0.0.1:8340/api/auth/setup/status ``` +Open `http://your-server:8340`, switch to English if desired, and create the first administrator on the **System setup** screen. For a custom listen port, run the installer with a matching `HEALTH_URL`. + ## Password reset If the admin password is lost: ```bash -/opt/backupx/backupx reset-password \ +/opt/backupx/bin/backupx reset-password \ --username admin \ --password 'newpass123' \ - --config /opt/backupx/config.yaml + --config /etc/backupx/config.yaml ``` Docker equivalent: diff --git a/docs-site/docs/getting-started/installation.md b/docs-site/docs/getting-started/installation.md index 32f0c85..42bec64 100644 --- a/docs-site/docs/getting-started/installation.md +++ b/docs-site/docs/getting-started/installation.md @@ -55,10 +55,11 @@ sudo ./install.sh # creates system user, installs to /opt/backupx, sets u The installer: 1. Creates a `backupx` system user -2. Installs binary to `/opt/backupx/backupx` -3. Creates `/opt/backupx/config.yaml` with safe defaults +2. Installs the binary to `/opt/backupx/bin/backupx` and the web console to `/opt/backupx/web` +3. Creates `/etc/backupx/config.yaml` with safe defaults 4. Installs and enables the `backupx.service` systemd unit 5. (Optional) Configures an Nginx reverse proxy +6. Waits for `/api/auth/setup/status`; if startup fails, prints systemd diagnostics and exits non-zero ## From source @@ -67,16 +68,17 @@ Requires Go ≥ 1.25 and Node.js ≥ 20. ```bash git clone https://github.com/Awuqing/BackupX.git && cd BackupX make build -# or, for builds behind the great firewall -make docker-cn +sudo ./deploy/install.sh ``` After `make build`, the binary is at `server/bin/backupx` and the built web UI is at `web/dist/`. +The installer consumes those exact paths, so no Docker runtime is required. If an existing configuration uses a non-default port, set `HEALTH_URL` for the readiness check, for example `sudo HEALTH_URL=http://127.0.0.1:9000/api/auth/setup/status ./deploy/install.sh`. ## Verify the install ```bash -backupx --version # e.g. v1.6.0 +/opt/backupx/bin/backupx --version +curl -fsS http://127.0.0.1:8340/api/auth/setup/status ``` -Then open `http://your-server:8340` to see the initial admin setup screen. +Then open `http://your-server:8340`. Choose **English** or **中文** in the upper-right corner. A fresh database shows **System setup**, where you create the first administrator username and password. If that form does not appear, retry the status request above before attempting to sign in. 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 60e49ba..f58191e 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 @@ -10,20 +10,21 @@ description: 从预编译包或源码部署 BackupX(systemd + Nginx)。 ```bash # 下载对应平台的压缩包 -curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-v1.6.0-linux-amd64.tar.gz +curl -LO https://github.com/Awuqing/BackupX/releases/latest/download/backupx-linux-amd64.tar.gz # 解压并安装 -tar xzf backupx-v*-linux-amd64.tar.gz && cd backupx-* +tar xzf backupx-linux-amd64.tar.gz && cd backupx-*-linux-amd64 sudo ./install.sh ``` 安装脚本自动完成以下步骤: 1. 创建系统用户 `backupx` -2. 复制二进制到 `/opt/backupx/` -3. 生成默认 `config.yaml`(含安全的 JWT/加密密钥) +2. 复制二进制到 `/opt/backupx/bin/backupx`,并把 Web 控制台复制到 `/opt/backupx/web` +3. 把默认配置安装到 `/etc/backupx/config.yaml` 4. 安装并启用 `backupx.service` systemd 单元 5. (可选)生成 Nginx 站点配置 — 参见 [Nginx 反向代理](./nginx) +6. 验证首次初始化接口就绪后才报告安装成功 如果要部署多节点集群,安装后请编辑 `/etc/backupx/config.yaml`,设置远程 Agent 可访问到的 Master URL: @@ -63,11 +64,13 @@ After=network.target [Service] Type=simple User=backupx +Group=backupx WorkingDirectory=/opt/backupx -ExecStart=/opt/backupx/backupx --config /opt/backupx/config.yaml +ExecStart=/opt/backupx/bin/backupx -config /etc/backupx/config.yaml Restart=on-failure -RestartSec=5s -LimitNOFILE=65536 +RestartSec=5 +NoNewPrivileges=true +LimitNOFILE=65535 [Install] WantedBy=multi-user.target @@ -79,17 +82,20 @@ WantedBy=multi-user.target sudo systemctl status backupx sudo journalctl -u backupx -f # 实时日志 sudo systemctl restart backupx +curl -fsS http://127.0.0.1:8340/api/auth/setup/status ``` +访问 `http://your-server:8340`,可按需切换到 English,然后在“系统初始化 / System setup”页面创建首个管理员。若监听端口不是默认值,请为安装脚本传入对应的 `HEALTH_URL`。 + ## 密码重置 忘记管理员密码时: ```bash -/opt/backupx/backupx reset-password \ +/opt/backupx/bin/backupx reset-password \ --username admin \ --password 'newpass123' \ - --config /opt/backupx/config.yaml + --config /etc/backupx/config.yaml ``` Docker 等效命令: diff --git a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md index 3ac0208..3fbac14 100644 --- a/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md +++ b/docs-site/i18n/zh-CN/docusaurus-plugin-content-docs/current/getting-started/installation.md @@ -55,10 +55,11 @@ sudo ./install.sh # 创建系统用户、安装到 /opt/backupx、配置 安装脚本会自动: 1. 创建 `backupx` 系统用户 -2. 安装二进制到 `/opt/backupx/backupx` -3. 生成 `/opt/backupx/config.yaml`(含安全默认值) +2. 安装二进制到 `/opt/backupx/bin/backupx`,并把 Web 控制台安装到 `/opt/backupx/web` +3. 生成 `/etc/backupx/config.yaml`(含安全默认值) 4. 注册并启用 `backupx.service` systemd 单元 5. (可选)配置 Nginx 反向代理 +6. 等待 `/api/auth/setup/status` 就绪;启动失败时输出 systemd 诊断并返回非零状态 ## 从源码构建 @@ -67,16 +68,17 @@ sudo ./install.sh # 创建系统用户、安装到 /opt/backupx、配置 ```bash git clone https://github.com/Awuqing/BackupX.git && cd BackupX make build -# 或使用国内镜像加速构建 Docker -make docker-cn +sudo ./deploy/install.sh ``` `make build` 完成后,二进制位于 `server/bin/backupx`,构建好的 Web UI 位于 `web/dist/`。 +安装脚本会直接使用这两个路径,不需要 Docker 运行时。如果已有配置修改了默认端口,可覆盖就绪检查地址,例如:`sudo HEALTH_URL=http://127.0.0.1:9000/api/auth/setup/status ./deploy/install.sh`。 ## 验证安装 ```bash -backupx --version # 输出如 v1.6.0 +/opt/backupx/bin/backupx --version +curl -fsS http://127.0.0.1:8340/api/auth/setup/status ``` -打开浏览器访问 `http://your-server:8340`,会进入初始化管理员账户页面。 +打开浏览器访问 `http://your-server:8340`,可在右上角选择 **中文** 或 **English**。全新数据库会显示“系统初始化 / System setup”,在这里创建首个管理员用户名和密码。如果没有出现初始化表单,请先重试上面的状态接口,不要直接尝试登录。 diff --git a/server/internal/installscript/deploy_install_test.go b/server/internal/installscript/deploy_install_test.go index 4c0507a..c001e23 100644 --- a/server/internal/installscript/deploy_install_test.go +++ b/server/internal/installscript/deploy_install_test.go @@ -39,3 +39,23 @@ func TestDeployInstallScriptSupportsReleasePackageLayout(t *testing.T) { } } } + +func TestDeployInstallScriptSupportsSourceBuildAndVerifiesFirstSetup(t *testing.T) { + scriptPath := filepath.Join("..", "..", "..", "deploy", "install.sh") + data, err := os.ReadFile(scriptPath) + if err != nil { + t.Fatal(err) + } + script := string(data) + for _, want := range []string{ + `SOURCE_BIN_DEFAULT="$PROJECT_ROOT/server/bin/backupx"`, + `For a source install, run 'make build' in the repository root first.`, + `HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:8340/api/auth/setup/status}"`, + `systemctl is-active --quiet "$SERVICE_NAME"`, + `System setup`, + } { + if !strings.Contains(script, want) { + t.Fatalf("install.sh missing %q", want) + } + } +} diff --git a/web/src/components/common/LanguageSwitcher.tsx b/web/src/components/common/LanguageSwitcher.tsx new file mode 100644 index 0000000..10a89f8 --- /dev/null +++ b/web/src/components/common/LanguageSwitcher.tsx @@ -0,0 +1,18 @@ +import { Select } from '@arco-design/web-react' +import { useTranslation } from 'react-i18next' +import { languageOptions, normalizeLanguage, setApplicationLanguage, type SupportedLanguage } from '../../i18n' + +export function LanguageSwitcher() { + const { t, i18n } = useTranslation() + const currentLanguage = normalizeLanguage(i18n.resolvedLanguage) + + return ( + } size="large" /> + + } size="large" /> - - } size="large" /> + + } size="large" /> - - } size="large" /> + + } size="large" /> - ) : ( form={loginForm} layout="vertical" onSubmit={handleLogin}> - - } size="large" onChange={resetTwoFactorPrompt} /> + + } size="large" onChange={resetTwoFactorPrompt} /> - - } size="large" onChange={resetTwoFactorPrompt} /> + + } size="large" onChange={resetTwoFactorPrompt} /> {twoFactorRequired && ( <> - - } size="large" maxLength={32} /> + + } size="large" maxLength={32} /> - - - + + + - 信任此设备 30 天 + {t('auth.trustDevice')} )} - )} diff --git a/web/src/styles/global.css b/web/src/styles/global.css index 3b58508..cf28e30 100644 --- a/web/src/styles/global.css +++ b/web/src/styles/global.css @@ -42,48 +42,25 @@ body { .login-bg { position: fixed; inset: 0; - background: linear-gradient(135deg, #111a2c 0%, #1f2d47 100%); + background: #111a2c; z-index: 0; } -.login-bg::before { - content: ''; - position: absolute; - width: 800px; - height: 800px; - border-radius: 50%; - background: radial-gradient(circle, rgba(52,145,250,0.08) 0%, transparent 70%); - top: -300px; - right: -200px; -} - -.login-bg::after { - content: ''; - position: absolute; - width: 600px; - height: 600px; - border-radius: 50%; - background: radial-gradient(circle, rgba(114,46,209,0.06) 0%, transparent 70%); - bottom: -200px; - left: -100px; -} - .login-container { display: flex; width: 1000px; max-width: 90vw; min-height: 560px; background: var(--color-bg-2); - border-radius: 20px; + border-radius: 4px; overflow: hidden; - box-shadow: 0 20px 60px rgba(0,0,0,0.4); z-index: 1; animation: slideUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); } .login-banner { flex: 1; - background: linear-gradient(135deg, var(--color-primary-6, #165dff) 0%, var(--color-primary-8, #0e42d2) 100%); + background: var(--color-primary-6, #165dff); position: relative; display: flex; align-items: center; diff --git a/web/src/test/setup.ts b/web/src/test/setup.ts index a866f69..a9a4ac5 100644 --- a/web/src/test/setup.ts +++ b/web/src/test/setup.ts @@ -24,3 +24,17 @@ Object.defineProperty(window, 'localStorage', { value: storage, configurable: true, }) + +Object.defineProperty(window, 'matchMedia', { + configurable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addEventListener: () => undefined, + removeEventListener: () => undefined, + addListener: () => undefined, + removeListener: () => undefined, + dispatchEvent: () => false, + }), +})