feat: 支持自定义订阅节点并兼容现有数据库
This commit is contained in:
@@ -1,166 +1,15 @@
|
||||
import Database from "better-sqlite3";
|
||||
import { migrateDatabase } from "@/lib/db-migrations";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { encryptText } from "@/lib/crypto";
|
||||
import { getEnv } from "@/lib/env";
|
||||
import { createOpaqueToken, createStableId, hashPassword } from "@/lib/password";
|
||||
|
||||
const MIGRATIONS = [
|
||||
`
|
||||
CREATE TABLE IF NOT EXISTS admins (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS admin_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
admin_id INTEGER NOT NULL,
|
||||
session_hash TEXT NOT NULL UNIQUE,
|
||||
expires_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
last_seen_at TEXT NOT NULL,
|
||||
FOREIGN KEY (admin_id) REFERENCES admins(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hy2_users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
auth_id TEXT NOT NULL UNIQUE,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
expires_at TEXT,
|
||||
traffic_limit_bytes INTEGER,
|
||||
used_tx_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
used_rx_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
notes TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hy2_nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
auth_token TEXT NOT NULL,
|
||||
traffic_stats_url TEXT NOT NULL,
|
||||
traffic_stats_secret TEXT NOT NULL,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
poll_interval_seconds INTEGER NOT NULL DEFAULT 15,
|
||||
last_polled_at TEXT,
|
||||
last_sync_ok_at TEXT,
|
||||
last_error_at TEXT,
|
||||
last_error_message TEXT,
|
||||
last_online_users INTEGER NOT NULL DEFAULT 0,
|
||||
last_stream_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS traffic_ledgers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
node_id INTEGER NOT NULL,
|
||||
user_id INTEGER,
|
||||
auth_id TEXT NOT NULL,
|
||||
tx_bytes INTEGER NOT NULL,
|
||||
rx_bytes INTEGER NOT NULL,
|
||||
window_started_at TEXT NOT NULL,
|
||||
window_ended_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (node_id) REFERENCES hy2_nodes(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES hy2_users(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS node_sync_records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
node_id INTEGER NOT NULL,
|
||||
started_at TEXT NOT NULL,
|
||||
finished_at TEXT NOT NULL,
|
||||
ok INTEGER NOT NULL,
|
||||
traffic_entries INTEGER NOT NULL DEFAULT 0,
|
||||
online_users INTEGER NOT NULL DEFAULT 0,
|
||||
stream_count INTEGER NOT NULL DEFAULT 0,
|
||||
kicked_auth_ids TEXT NOT NULL DEFAULT '',
|
||||
error_message TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (node_id) REFERENCES hy2_nodes(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_audits (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
node_id INTEGER,
|
||||
user_id INTEGER,
|
||||
auth_id TEXT,
|
||||
username TEXT,
|
||||
addr TEXT,
|
||||
requested_tx INTEGER,
|
||||
ok INTEGER NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (node_id) REFERENCES hy2_nodes(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES hy2_users(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS node_user_presence (
|
||||
node_id INTEGER NOT NULL,
|
||||
user_id INTEGER,
|
||||
auth_id TEXT NOT NULL,
|
||||
connections INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TEXT NOT NULL,
|
||||
PRIMARY KEY (node_id, auth_id),
|
||||
FOREIGN KEY (node_id) REFERENCES hy2_nodes(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES hy2_users(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_hy2_users_auth_id ON hy2_users(auth_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_hy2_users_username ON hy2_users(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_hy2_nodes_enabled ON hy2_nodes(enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_traffic_ledgers_auth_id ON traffic_ledgers(auth_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_traffic_ledgers_created_at ON traffic_ledgers(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_node_sync_records_node_id ON node_sync_records(node_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_auth_audits_created_at ON auth_audits(created_at DESC);
|
||||
`,
|
||||
`
|
||||
ALTER TABLE hy2_users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE hy2_users ADD COLUMN auth_secret_encrypted TEXT;
|
||||
ALTER TABLE hy2_users ADD COLUMN subscription_token TEXT;
|
||||
ALTER TABLE hy2_users ADD COLUMN subscription_label TEXT NOT NULL DEFAULT '';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
session_hash TEXT NOT NULL UNIQUE,
|
||||
expires_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
last_seen_at TEXT NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES hy2_users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE hy2_nodes ADD COLUMN server_host TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE hy2_nodes ADD COLUMN server_port INTEGER NOT NULL DEFAULT 443;
|
||||
ALTER TABLE hy2_nodes ADD COLUMN client_name TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE hy2_nodes ADD COLUMN bandwidth_up_mbps INTEGER NOT NULL DEFAULT 30;
|
||||
ALTER TABLE hy2_nodes ADD COLUMN bandwidth_down_mbps INTEGER NOT NULL DEFAULT 30;
|
||||
ALTER TABLE hy2_nodes ADD COLUMN skip_cert_verify INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE hy2_nodes ADD COLUMN sni TEXT;
|
||||
ALTER TABLE hy2_nodes ADD COLUMN traffic_stats_listen TEXT NOT NULL DEFAULT ':9999';
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_hy2_users_subscription_token ON hy2_users(subscription_token);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id);
|
||||
`,
|
||||
`
|
||||
ALTER TABLE hy2_nodes ADD COLUMN hy2_listen TEXT NOT NULL DEFAULT ':443';
|
||||
`,
|
||||
];
|
||||
import {
|
||||
createOpaqueToken,
|
||||
createStableId,
|
||||
hashPassword,
|
||||
} from "@/lib/password";
|
||||
|
||||
declare global {
|
||||
var __hy2PanelDb: Database.Database | undefined;
|
||||
@@ -183,12 +32,7 @@ function createDbInstance() {
|
||||
}
|
||||
|
||||
async function initializeDb(db: Database.Database) {
|
||||
const currentVersion = db.pragma("user_version", { simple: true }) as number;
|
||||
|
||||
for (let index = currentVersion; index < MIGRATIONS.length; index += 1) {
|
||||
db.exec(MIGRATIONS[index]);
|
||||
db.pragma(`user_version = ${index + 1}`);
|
||||
}
|
||||
migrateDatabase(db);
|
||||
|
||||
const env = getEnv();
|
||||
const now = new Date().toISOString();
|
||||
@@ -263,7 +107,8 @@ async function initializeDb(db: Database.Database) {
|
||||
username: env.ADMIN_USERNAME,
|
||||
passwordHash,
|
||||
secret: encryptText(env.ADMIN_PASSWORD),
|
||||
subscriptionToken: existingAdminUser.subscription_token ?? createOpaqueToken(),
|
||||
subscriptionToken:
|
||||
existingAdminUser.subscription_token ?? createOpaqueToken(),
|
||||
now,
|
||||
});
|
||||
} else {
|
||||
@@ -291,7 +136,9 @@ async function initializeDb(db: Database.Database) {
|
||||
}
|
||||
|
||||
const usersMissingTokens = db
|
||||
.prepare("SELECT id, username FROM hy2_users WHERE subscription_token IS NULL")
|
||||
.prepare(
|
||||
"SELECT id, username FROM hy2_users WHERE subscription_token IS NULL",
|
||||
)
|
||||
.all() as Array<{ id: number; username: string }>;
|
||||
|
||||
const updateUserToken = db.prepare(
|
||||
|
||||
Reference in New Issue
Block a user