清理无用代码
This commit is contained in:
1 parent
26d9897c26
commit
f571da637f
10 files changed
+38
-112
No files matched your search
@@ -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`。
|
||||
|
||||
## 从源码开发
|
||||
|
||||
|
||||
Generated
-28
@@ -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
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -201,7 +201,7 @@ impl Database {
|
||||
MigrateError::VersionMissing(_) | MigrateError::VersionMismatch(_)
|
||||
) {
|
||||
return Err(anyhow::Error::new(error).context(
|
||||
"数据库迁移基线不匹配;请保留原数据库,使用原版本导出完整结构的 ZIP 备份,配置全新数据库后再恢复;不要删除或修改 _sqlx_migrations",
|
||||
"数据库结构与当前版本不匹配;请使用匹配版本的程序,或配置全新数据库",
|
||||
));
|
||||
}
|
||||
return Err(error.into());
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in new issue
Block a user