清理无用代码

This commit is contained in:
imengying committed 2026-09-08 11:43:33 +08:00
1 parent 26d9897c26
commit f571da637f
10 files changed
+38 -112

No files matched your search

+2 -13
View File
@@ -50,7 +50,7 @@ nodeflare.example.com {
只有 `trusted_proxies` 中列出的代理写入的 `X-Forwarded-For` 会被信任,代理不在本机时将其 IP 或 CIDR 加入 `config.toml`。
更新:同一数据库基线内重新运行安装脚本即可,已安装的配置和数据会保留;也可直接执行 `install.sh --install`(Windows 为 `install.ps1 -Install`)。从旧迁移 `01–04` 切换到本版前,必须先按[数据库基线切换](#数据库基线切换)导出备份并准备全新数据库。卸载默认保留配置和数据,追加 `--purge` 一并删除:
更新:重新运行安装脚本即可,已安装的配置和数据会保留;也可直接执行 `install.sh --install`(Windows 为 `install.ps1 -Install`)。卸载默认保留配置和数据,追加 `--purge` 一并删除:
```bash
# Linux / macOS
@@ -178,18 +178,7 @@ database_url = "postgres://nodeflare:password@127.0.0.1:5432/nodeflare?sslmode=d
- 历史指标默认保留 30 天,过期数据自动清理;完整空间回收在后台手动触发。
- 升级建议先更新服务端再更新 Agent;恢复备份建议使用相同版本。
### 数据库基线切换
本版按新项目维护,SQLite / PostgreSQL 均只有一个包含完整结构的 `01_initial.sql`。Agent 协议为 `2`,不再接受旧协议或缺少 `persist` 标志的上报,服务端与 Agent 必须配套更新。
从已经执行旧迁移 `01–04` 的版本切换时:
1. **更新前**在原版本后台导出 ZIP 备份,并保留原程序、配置和数据库以便回退。支持该版本导出的完整表结构备份(格式版本 `2`),不支持更早缺少历史聚合字段的备份。
2. 停止服务端,将 `database_url` 指向全新的 SQLite 文件或空 PostgreSQL 数据库。不要覆盖原库、清空迁移记录,或把旧 `.db` / SQL 转储直接作为新库使用。
3. 安装本版,用临时管理员账号初始化新库;原配置中的管理员密码已被清空时,需要重新设置 `admin_username`、`admin_password`。登录新后台后恢复刚导出的 ZIP。
4. 恢复会覆盖临时账号和新库业务数据,并退出登录。使用备份中的管理员账号、密码和两步验证重新登录,再更新各节点 Agent;使用额外安装 Token 的节点还需重新安装以更换 Token。
ZIP 恢复只导入业务数据,不导入 `_sqlx_migrations`,新库仍保留单一 `01` 基线。现版本备份中未填充的历史统计字段会在恢复事务内补齐;设置、节点、历史记录、主题文件和安全配置会保留。全部验证正常前不要删除原库。
SQLite / PostgreSQL 均使用一份 `01_initial.sql`,首次启动时自动创建完整数据库结构。ZIP 恢复校验表结构并导入业务数据,保留目标库的初始化记录。服务端与 Agent 须配套使用,当前 Agent 协议为 `2`。
## 从源码开发
-28
View File
@@ -113,7 +113,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [
"axum-core",
"axum-macros",
"base64",
"bytes",
"form_urlencoded",
@@ -162,17 +161,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "axum-macros"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "base64"
version = "0.22.1"
@@ -446,9 +434,6 @@ name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"serde_core",
]
[[package]]
name = "digest"
@@ -1951,7 +1936,6 @@ dependencies = [
"serde_json",
"sha2 0.10.9",
"sqlx-core",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn 2.0.119",
@@ -2169,7 +2153,6 @@ dependencies = [
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
@@ -2178,16 +2161,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "time-macros"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.8.4"
@@ -2222,7 +2195,6 @@ dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
+4 -4
View File
@@ -11,18 +11,18 @@ name = "nodeflare"
path = "src/main.rs"
[dependencies]
axum = { version = "0.8.9", features = ["ws", "macros"] }
tokio = { version = "1", features = ["full"] }
axum = { version = "0.8.9", features = ["ws"] }
tokio = { version = "1", features = ["fs", "macros", "net", "rt-multi-thread", "signal", "sync", "time"] }
tokio-util = { version = "0.7", features = ["io"] }
tower-http = { version = "0.7.1", features = ["trace"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "tls-rustls-ring", "any", "sqlite-bundled", "postgres", "migrate", "macros", "json"] }
sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "tls-rustls-ring", "any", "sqlite-bundled", "postgres", "migrate", "macros"] }
toml = "1.1.5"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
futures-util = "0.3"
time = { version = "0.3", features = ["serde", "formatting", "parsing"] }
time = "0.3"
sha2 = "0.11"
pbkdf2 = "0.13"
totp-lite = "2"
+1 -12
View File
@@ -99,20 +99,10 @@ CREATE INDEX metric_history_time ON metric_history(timestamp);
CREATE TABLE server_latest_state (
server_id TEXT PRIMARY KEY REFERENCES servers(id) ON DELETE CASCADE,
latest_timestamp BIGINT NOT NULL,
cpu DOUBLE PRECISION NOT NULL DEFAULT 0,
mem_used BIGINT NOT NULL DEFAULT 0,
mem_total BIGINT NOT NULL DEFAULT 0,
disk_used BIGINT NOT NULL DEFAULT 0,
disk_total BIGINT NOT NULL DEFAULT 0,
net_in DOUBLE PRECISION NOT NULL DEFAULT 0,
net_out DOUBLE PRECISION NOT NULL DEFAULT 0,
uptime BIGINT NOT NULL DEFAULT 0,
latest_json TEXT NOT NULL DEFAULT '{}',
last_batch_id TEXT NOT NULL DEFAULT ''
);
CREATE INDEX server_latest_state_time ON server_latest_state(latest_timestamp);
CREATE TABLE admin_2fa (
username TEXT PRIMARY KEY,
totp_secret TEXT NOT NULL,
@@ -133,8 +123,7 @@ CREATE TABLE remote_tasks (
exit_code BIGINT
);
CREATE INDEX remote_tasks_server_status_time
ON remote_tasks(server_id, status, requested_at);
CREATE INDEX remote_tasks_server ON remote_tasks(server_id);
CREATE INDEX remote_tasks_status_completed
ON remote_tasks(status, completed_at);
+1 -12
View File
@@ -102,20 +102,10 @@ CREATE INDEX metric_history_time ON metric_history(timestamp);
CREATE TABLE server_latest_state (
server_id TEXT PRIMARY KEY REFERENCES servers(id) ON DELETE CASCADE,
latest_timestamp INTEGER NOT NULL,
cpu REAL NOT NULL DEFAULT 0,
mem_used INTEGER NOT NULL DEFAULT 0,
mem_total INTEGER NOT NULL DEFAULT 0,
disk_used INTEGER NOT NULL DEFAULT 0,
disk_total INTEGER NOT NULL DEFAULT 0,
net_in REAL NOT NULL DEFAULT 0,
net_out REAL NOT NULL DEFAULT 0,
uptime INTEGER NOT NULL DEFAULT 0,
latest_json TEXT NOT NULL DEFAULT '{}',
last_batch_id TEXT NOT NULL DEFAULT ''
) WITHOUT ROWID;
CREATE INDEX server_latest_state_time ON server_latest_state(latest_timestamp);
CREATE TABLE admin_2fa (
username TEXT PRIMARY KEY,
totp_secret TEXT NOT NULL,
@@ -136,8 +126,7 @@ CREATE TABLE remote_tasks (
exit_code INTEGER
) WITHOUT ROWID;
CREATE INDEX remote_tasks_server_status_time
ON remote_tasks(server_id, status, requested_at);
CREATE INDEX remote_tasks_server ON remote_tasks(server_id);
CREATE INDEX remote_tasks_status_completed
ON remote_tasks(status, completed_at);
+1 -1
View File
@@ -201,7 +201,7 @@ impl Database {
MigrateError::VersionMissing(_) | MigrateError::VersionMismatch(_)
) {
return Err(anyhow::Error::new(error).context(
"数据库迁移基线不匹配;请保留原数据库,使用原版本导出完整结构的 ZIP 备份,配置全新数据库后再恢复;不要删除或修改 _sqlx_migrations",
"数据库结构与当前版本不匹配;请使用匹配版本的程序,或配置全新数据库",
));
}
return Err(error.into());
+4 -16
View File
@@ -717,25 +717,13 @@ pub async fn save_agent_batch(
let persisted_through = latest.timestamp;
let latest_json = serde_json::to_string(latest)?;
sqlx::query(db.sql(
"INSERT INTO server_latest_state(server_id, latest_timestamp, cpu, mem_used, mem_total, \
disk_used, disk_total, net_in, net_out, uptime, latest_json, last_batch_id) \
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) \
"INSERT INTO server_latest_state(server_id, latest_timestamp, latest_json, last_batch_id) \
VALUES (?, ?, ?, ?) \
ON CONFLICT(server_id) DO UPDATE SET latest_timestamp=excluded.latest_timestamp, \
cpu=excluded.cpu, mem_used=excluded.mem_used, mem_total=excluded.mem_total, \
disk_used=excluded.disk_used, disk_total=excluded.disk_total, net_in=excluded.net_in, \
net_out=excluded.net_out, uptime=excluded.uptime, latest_json=excluded.latest_json, \
last_batch_id=excluded.last_batch_id",
latest_json=excluded.latest_json, last_batch_id=excluded.last_batch_id",
))
.bind(&identity.server_id)
.bind(latest.timestamp)
.bind(latest.cpu)
.bind(latest.mem_used)
.bind(latest.mem_total)
.bind(latest.disk_used)
.bind(latest.disk_total)
.bind(latest.net_in)
.bind(latest.net_out)
.bind(latest.uptime)
.bind(latest_json)
.bind(batch_id)
.execute(&mut *transaction)
@@ -3155,7 +3143,7 @@ mod tests {
"SELECT name FROM sqlite_master WHERE type='index' AND name IN (\
'metric_history_time',\
'latency_results_time',\
'remote_tasks_server_status_time',\
'remote_tasks_server',\
'servers_public_sort')",
)
.fetch_all(db.pool())
+25 -16
View File
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { delimiter, join } from "node:path";
import { spawnSync } from "node:child_process";
const installer = readFileSync(new URL("../../install.sh", import.meta.url), "utf8");
@@ -294,21 +294,30 @@ describe("agent installer service safety", () => {
test("a failed stop of an existing service prevents replacement", () => {
const stop = agentInstaller.slice(agentInstaller.indexOf(" rollback_agent=true\n"), agentInstaller.indexOf(' mv "$temporary" "$AGENT_FILE"'));
expect(stop).toContain("systemctl stop");
for (const init of ["systemd", "openrc"]) {
const result = spawnSync("sh", ["-c", `
set -eu
${agentShellFunctions("fail")}
init_system=$TEST_INIT
had_service=true
SERVICE_NAME=nodeflare-agent
systemctl() { return 1; }
rc-service() { return 1; }
${stop}
printf 'replaced'
`], { env: { ...process.env, TEST_INIT: init }, encoding: "utf8" });
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("未替换程序");
const directory = mkdtempSync(join(tmpdir(), "nodeflare-agent-stop-"));
const log = join(directory, "stop.log");
try {
// Dash rejects hyphenated function names, so mock the external commands.
for (const command of ["systemctl", "rc-service"]) {
writeFileSync(join(directory, command), '#!/bin/sh\nprintf \'%s\\n\' "$*" > "$TEST_STOP_LOG"\nexit 1\n', { mode: 0o755 });
}
for (const init of ["systemd", "openrc"]) {
const result = spawnSync("sh", ["-c", `
set -eu
${agentShellFunctions("fail")}
init_system=$TEST_INIT
had_service=true
SERVICE_NAME=nodeflare-agent
${stop}
printf 'replaced'
`], { env: { ...process.env, PATH: `${directory}${delimiter}${process.env.PATH ?? ""}`, TEST_INIT: init, TEST_STOP_LOG: log }, encoding: "utf8" });
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("未替换程序");
expect(readFileSync(log, "utf8")).toBe(init === "systemd" ? "stop nodeflare-agent\n" : "nodeflare-agent stop\n");
}
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
-6
View File
@@ -115,7 +115,6 @@
.section-head span { color: var(--admin-muted); font-size: 12px; }
.row-actions { display: flex; align-items: center; gap: 3px; }
.section-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; margin: -1px; clip: rect(0, 0, 0, 0); white-space: nowrap; }
.batch-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; justify-content: space-between; min-height: 44px; border-block: 1px solid var(--admin-border); color: var(--admin-muted); font-size: 13px; }
.select-all { display: inline-flex; align-items: center; flex-direction: row; gap: 7px; color: var(--admin-text); font-size: 13px; }
@@ -409,8 +408,6 @@
.task-status.success { background: var(--admin-success-bg); color: var(--admin-success); }
.task-status.failed { background: var(--admin-danger-bg); color: var(--admin-danger); }
.task-time { font-size: 12px; color: var(--admin-muted); margin-left: auto; }
.task-command { margin: 8px 0; }
.task-command strong { color: var(--admin-text); font-size: 13px; font-family: ui-monospace, monospace; }
.remote-auto-refresh { display: flex; align-items: center; gap: 5px; margin-top: 5px; }
.remote-auto-refresh svg { animation: remote-refresh-spin 1.2s linear infinite; }
.remote-2fa-required { display: grid; align-items: center; gap: 10px; border: 1px solid color-mix(in srgb, var(--admin-warning) 35%, var(--admin-border)); border-radius: 7px; padding: 12px; background: color-mix(in srgb, var(--admin-warning) 7%, var(--admin-surface)); grid-template-columns: auto minmax(0, 1fr) auto; }
@@ -421,9 +418,6 @@
.task-progress { margin: 9px 0 0; color: var(--admin-muted); font-size: 12px; }
.task-result { overflow-x: auto; margin: 10px 0 0; border: 1px solid var(--admin-border); border-radius: 6px; padding: 12px; background: var(--admin-bg); font-size: 12px; }
.task-result.failed { border-color: color-mix(in srgb, var(--admin-danger) 38%, var(--admin-border)); }
.task-item details { margin-top: 10px; }
.task-item summary { cursor: pointer; color: var(--admin-accent); font-size: 13px; font-weight: 500; }
.task-item summary:hover { text-decoration: underline; }
@keyframes remote-refresh-spin { to { transform: rotate(360deg); } }
@media (max-width: 639px) {
-4
View File
@@ -3,7 +3,6 @@
--foreground: #11191f;
--card-solid: #f8fafb;
--card: rgb(248 250 251 / 0.78);
--card-hover: rgb(255 255 255 / 0.92);
--node-card-bg: rgb(241 245 249 / 0.74);
--node-card-hover: rgb(248 250 252 / 0.8);
--node-card-border: rgb(226 232 240 / 0.68);
@@ -24,7 +23,6 @@
--info: #2563b9;
--chart-grid: rgb(83 104 116 / 0.15);
--shadow: 0 10px 32px rgb(31 48 58 / 0.09), inset 0 1px 0 rgb(255 255 255 / 0.58);
--node-offline-bg: rgb(255 255 255 / 0.2);
/* Card geometry lives here so the grid and the latency panels cannot drift
apart and make rows jump as data arrives. */
--node-card-min-height: 320px;
@@ -43,7 +41,6 @@
--foreground: #f3f6f5;
--card-solid: #161d1e;
--card: rgb(22 29 30 / 0.86);
--card-hover: rgb(28 36 37 / 0.94);
--node-card-bg: rgb(22 29 30 / 0.86);
--node-card-hover: rgb(26 34 35 / 0.93);
--node-card-border: rgb(255 255 255 / 0.16);
@@ -63,7 +60,6 @@
--info: #69a7e8;
--chart-grid: rgb(218 234 230 / 0.14);
--shadow: 0 12px 36px rgb(0 0 0 / 0.28), inset 0 1px 0 rgb(255 255 255 / 0.035);
--node-offline-bg: rgb(0 0 0 / 0.2);
}
* { box-sizing: border-box; scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--foreground) 18%, transparent) transparent; }