Data Model / SQLite 数据模型
Zeno 使用 SQLite。schema 不兼容任何旧系统。
nodes
服务器 / Agent 节点。
CREATE TABLE nodes (
id TEXT PRIMARY KEY,
display_name TEXT NOT NULL,
token_hash TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'no_data',
country_code TEXT,
region TEXT,
expiry_date TEXT,
billing_cycle TEXT,
renewal_amount REAL,
renewal_currency TEXT NOT NULL DEFAULT 'CNY',
display_order INTEGER NOT NULL DEFAULT 0,
public_ipv4 TEXT,
public_ipv6 TEXT,
billing_mode TEXT NOT NULL DEFAULT 'both',
monthly_quota_bytes INTEGER,
monthly_reset_day INTEGER NOT NULL DEFAULT 1,
disabled INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
last_seen_at INTEGER
);说明:
country_code用于国旗展示。expiry_date/billing_cycle用于后台和首页展示到期/账单信息。renewal_amount/renewal_currency保存每台服务器的原币种续费金额;公开 Summary 额外给出按当天 Google Finance 汇率折算后的人民币月均消费。display_order控制首页卡片和后台列表排序。public_ipv4/public_ipv6可由后台编辑,也会由新 Agent best-effort 自动识别后上报;识别失败不会清空已有值。billing_mode控制月流量口径:both、in、out、max。monthly_reset_day控制账单周期从每月第几天开始,范围 1–31。token_hash只存 hash,不通过 API 返回。
exchange_rates
持久化最近一次从 Google Finance 成功获取的人民币汇率,避免外部汇率页面暂时不可用时让金额统计消失。
CREATE TABLE exchange_rates (
currency TEXT PRIMARY KEY,
cny_rate REAL NOT NULL,
source_date TEXT NOT NULL DEFAULT '',
updated_at INTEGER NOT NULL
);Controller 启动时立即刷新一次,之后每 24 小时刷新;只有完整响应通过校验后才原子替换缓存。
host_info
相对低频上报的主机信息。
CREATE TABLE host_info (
node_id TEXT PRIMARY KEY REFERENCES nodes(id),
hostname TEXT,
os_name TEXT,
os_version TEXT,
kernel TEXT,
arch TEXT,
virtualization TEXT,
cpu_model TEXT,
cpu_cores INTEGER,
memory_total_bytes INTEGER,
disk_total_bytes INTEGER,
boot_time INTEGER,
agent_version TEXT,
updated_at INTEGER NOT NULL
);state_samples
实时资源状态样本。
CREATE TABLE state_samples (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL REFERENCES nodes(id),
ts INTEGER NOT NULL,
cpu_percent REAL,
load1 REAL,
load5 REAL,
load15 REAL,
memory_used_bytes INTEGER,
memory_total_bytes INTEGER,
swap_used_bytes INTEGER,
swap_total_bytes INTEGER,
disk_used_bytes INTEGER,
disk_total_bytes INTEGER,
net_in_total_bytes INTEGER,
net_out_total_bytes INTEGER,
net_in_speed_bps REAL,
net_out_speed_bps REAL,
process_count INTEGER,
tcp_connection_count INTEGER,
uptime_seconds INTEGER
);
CREATE INDEX idx_state_samples_node_ts ON state_samples(node_id, ts);超过 26 小时的资源样本会事务性折叠到 state_history_rollups:每个节点每 30 秒一行,对每项指标分别保存 sum + count,因此旧 Agent 缺失字段仍保持 null 语义。7 天和 30 天查询把 raw 与 rollup 统一再次分桶,接口精度和时间范围不变。
traffic_monthly
月流量累计。Controller 根据 state 的累计 counter delta 更新;month 是该节点当前计费周期开始月份(例如 monthly_reset_day=15 且当前周期为 2026-06-15 至 2026-07-14 时,month=2026-06)。
CREATE TABLE traffic_monthly (
node_id TEXT NOT NULL REFERENCES nodes(id),
month TEXT NOT NULL,
billing_epoch INTEGER NOT NULL DEFAULT 0,
reset_day INTEGER NOT NULL DEFAULT 1,
billing_mode TEXT NOT NULL DEFAULT 'both',
in_bytes INTEGER NOT NULL DEFAULT 0,
out_bytes INTEGER NOT NULL DEFAULT 0,
billable_bytes INTEGER NOT NULL DEFAULT 0,
last_in_total_bytes INTEGER,
last_out_total_bytes INTEGER,
last_sample_ts INTEGER,
updated_at INTEGER NOT NULL,
PRIMARY KEY (node_id, month, billing_epoch)
);traffic_lifetime
首页永久累计流量。首次有效 state 样本把当时的网卡 counter 作为起点,之后按 counter delta 累计;counter 因服务器、Agent 或网卡重启而降低时,把重置后的较小 counter 视为重置后已经产生的流量并计入永久累计,同时将其设为下一次采样的基线。因此 in_bytes / out_bytes 不会因重启倒退或漏掉重置后的首次上报,也不受月重置日、计费口径或 billing epoch 变化影响。
旧数据库升级时,Controller 仅使用每台节点最新的有效 raw counter 进行一次性回填,并以该样本作为后续基线,不尝试从可能已裁剪的历史记录中重建永久累计值。
CREATE TABLE traffic_lifetime (
node_id TEXT PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
in_bytes INTEGER NOT NULL DEFAULT 0,
out_bytes INTEGER NOT NULL DEFAULT 0,
last_in_total_bytes INTEGER,
last_out_total_bytes INTEGER,
last_sample_ts INTEGER,
updated_at INTEGER NOT NULL
);probe_targets
探测目标。
CREATE TABLE probe_targets (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL,
address TEXT NOT NULL,
port INTEGER,
count INTEGER NOT NULL,
timeout_ms INTEGER NOT NULL,
interval_sec INTEGER NOT NULL,
display_order INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);type 当前支持:
tcping:TCP connect,必须带port。ping:ICMP ping,不带port。http_get:HTTP/HTTPS GET,不带port。
display_order 控制后台延迟监控列表、Agent 目标下发顺序、服务详情入口顺序和同一时间点的 Public latency series 展示顺序。历史数据库中仍为 0 的目标会在启动迁移时追加到已有正数顺序之后,并按 created_at、id 归一化为稳定的 10、20、30…;新目标未提交顺序时在创建事务中追加到当前最大顺序之后。探测目标创建后即为有效目标,不提供全局启用/停用状态;是否由某台服务器执行只由 node_probe_targets.enabled 控制。
node_probe_targets
哪些节点执行哪些探测目标。
CREATE TABLE node_probe_targets (
node_id TEXT NOT NULL REFERENCES nodes(id),
target_id TEXT NOT NULL REFERENCES probe_targets(id),
enabled INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (node_id, target_id)
);probe_rounds / probe_samples
每轮探测摘要和 raw samples。 Public 服务详情页按 target_id 查询所有节点的 probe_rounds,把同一监控服务拆成多条节点曲线。
CREATE TABLE probe_rounds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id TEXT NOT NULL REFERENCES nodes(id),
target_id TEXT NOT NULL REFERENCES probe_targets(id),
ts INTEGER NOT NULL,
type TEXT NOT NULL,
sent INTEGER NOT NULL,
received INTEGER NOT NULL,
loss_percent REAL NOT NULL,
min_ms REAL,
avg_ms REAL,
median_ms REAL,
max_ms REAL,
stddev_ms REAL,
error TEXT
);
CREATE INDEX idx_probe_rounds_node_target_ts ON probe_rounds(node_id, target_id, ts);
CREATE TABLE probe_samples (
round_id INTEGER NOT NULL REFERENCES probe_rounds(id) ON DELETE CASCADE,
seq INTEGER NOT NULL,
success INTEGER NOT NULL,
latency_ms REAL,
error TEXT,
PRIMARY KEY (round_id, seq)
);超过 26 小时的探测轮次会事务性折叠到 latency_history_rollups:每个节点、目标每分钟一行,分别保存 median、average、loss 的 sum + count。同一事务随后删除对应 probe_rounds,关联的 probe_samples 通过外键级联删除;进程中断不会留下重复汇总或先删后丢数据。
Controller 首次创建 rollup schema 后保留 24 小时回滚宽限期;期间继续保留完整 30 天 raw,上一版本仍能读取全部支持范围。宽限期结束后,每小时以有界批次逐步维护 raw 与 rollup,只保留最近 30 天的汇总历史。30 天边界按各自 bucket 向下对齐后保留;配置、当前状态、月流量和通知事件标记不属于高频历史,不随该任务删除。这样 1 天视图继续使用原始精度,7 天/30 天视图保持原有 7 分钟、30 分钟以及资源 30 分钟、2 小时网格,同时显著减少长期 raw sample 与索引写放大。
notification_channels / notification_types
通知当前是 Telegram-only 产品路径;SQLite schema 不保留多渠道 type / channel_type 兼容列。
CREATE TABLE notification_channels (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
destination TEXT NOT NULL,
credential TEXT NOT NULL,
delivery_version INTEGER NOT NULL DEFAULT 1,
destination_fingerprint TEXT NOT NULL DEFAULT '',
enabled INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE notification_types (
event_type TEXT PRIMARY KEY,
enabled INTEGER NOT NULL DEFAULT 0,
updated_at INTEGER NOT NULL
);
CREATE TABLE notification_deliveries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id TEXT NOT NULL DEFAULT '',
event_type TEXT NOT NULL,
label TEXT NOT NULL DEFAULT '',
node_id TEXT NOT NULL DEFAULT '',
node_name TEXT NOT NULL DEFAULT '',
node_ip TEXT NOT NULL DEFAULT '',
previous_status TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT '',
event_ts TEXT NOT NULL DEFAULT '',
detail TEXT NOT NULL DEFAULT '',
channel_id TEXT NOT NULL,
channel_name TEXT NOT NULL DEFAULT '',
channel_version INTEGER NOT NULL DEFAULT 1,
destination_fingerprint TEXT NOT NULL DEFAULT '',
state TEXT NOT NULL DEFAULT 'pending',
attempts INTEGER NOT NULL DEFAULT 0,
next_attempt_at INTEGER NOT NULL,
last_error TEXT NOT NULL DEFAULT '',
lease_until INTEGER NOT NULL DEFAULT 0,
claim_token TEXT NOT NULL DEFAULT '',
causal_predecessor_event_id TEXT NOT NULL DEFAULT '',
superseded_by_event_id TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
delivered_at INTEGER
);credential 不通过 Admin API 响应返回;delivery 也不复制 destination 或 credential,只绑定 channel_version 和不可逆 destination fingerprint。渠道目标、凭据或启用状态变化,以及渠道删除, 都会把旧 generation 的积压明确变成 terminal canceled;历史清理可删除该状态。 notification_types 仅保留旧 API 兼容,发送开关以 alert_rules.enabled 为准;兼容 PATCH 的两表写入 在同一事务内完成。
通知事件与 incident/续费按日去重标记在同一事务中写入 outbox;同一渠道、节点和事件流通过 causal_predecessor_event_id 串联。未开始尝试的离线可由恢复 supersede;一旦离线投递开始尝试, 恢复必须等待,前驱最终失败时恢复转为 canceled。event_ts 保存原事件时间,重试沿用稳定 event_id。
发送语义是 at-least-once:单次只 claim 一条,5 秒发送超时小于 30 秒 lease;ack 使用 claim token 并检查 RowsAffected。远端已接受后 Controller 在 ack 前崩溃仍可能重发,支持去重的 接收端应使用稳定 event_id。失败按退避策略最多尝试 5 次,Controller 重启后继续处理;投递 历史只保存净化后的错误和不可逆绑定,不保存 Bot Token、明文接收目标或请求 URL。
alert_rules / alert_rule_node_scopes / alert_rule_renewal_days / alert_rule_states
通知类型规则和内部命中状态。
CREATE TABLE alert_rules (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
metric TEXT NOT NULL,
comparator TEXT NOT NULL,
threshold REAL NOT NULL,
threshold_unit TEXT NOT NULL,
duration_sec INTEGER NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
notification_event_type TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
sort_order INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX idx_alert_rules_sort_order ON alert_rules(sort_order ASC, id ASC);
CREATE TABLE alert_rule_node_scopes (
rule_id TEXT NOT NULL REFERENCES alert_rules(id) ON DELETE CASCADE,
node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
created_at INTEGER NOT NULL,
PRIMARY KEY (rule_id, node_id)
);
CREATE INDEX idx_alert_rule_node_scopes_node ON alert_rule_node_scopes(node_id, rule_id);
CREATE TABLE alert_rule_renewal_days (
rule_id TEXT NOT NULL REFERENCES alert_rules(id) ON DELETE CASCADE,
days INTEGER NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (rule_id, days)
);
CREATE TABLE alert_rule_states (
node_id TEXT NOT NULL REFERENCES nodes(id),
rule_id TEXT NOT NULL REFERENCES alert_rules(id),
active INTEGER NOT NULL DEFAULT 0,
first_seen_at INTEGER,
last_seen_at INTEGER,
last_value REAL,
updated_at INTEGER NOT NULL,
PRIMARY KEY (node_id, rule_id)
);
CREATE INDEX idx_alert_rule_states_node_active ON alert_rule_states(node_id, active);alert_rule_node_scopes 没有记录时表示规则作用于全部服务器;有记录时只作用于指定服务器。 alert_rule_renewal_days 保存续费规则的多个提前提醒时间;每个 (rule_id, days) 唯一。alert_rules.threshold 继续保存最大提前天数,供旧客户端兼容读取和单值更新。
settings
全局设置,包含站点标题、Logo、主题、Agent 接入 URL、桌面/手机背景图配置和后台密码 hash。
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
);当前主要 key:
site_titlelogo_urlthemeagent_controller_urlbackground_url(兼容)desktop_background_urlmobile_background_urlappearance_presetcard_opacitycard_blurcard_radiusborder_strengthshadow_strengthbackground_overlaytheme_colorcustom_code:公开页面 CSS-only 外观扩展内容;前端只提取<style>或纯 CSS,不执行脚本/事件处理器。admin_username:单管理员账号名,默认admin。admin_password_hash:单管理员密码 hash。首次部署未设置时可用 bootstrap admin token 登录;修改账号或密码后以后以这些设置为准。
admin_sessions
后台单管理员登录 session。只存 session token hash,不存明文。
CREATE TABLE admin_sessions (
token_hash TEXT PRIMARY KEY,
created_at INTEGER NOT NULL,
last_seen_at INTEGER NOT NULL
);迁移策略
ensureSchema 会在启动时创建缺失表,并通过 additive ALTER TABLE ... ADD COLUMN 补齐新增列。现阶段只做向前兼容 additive migration,不兼容旧系统 DB。