tools: 加 cmd/dumpdb ops 工具
dumpdb 是 read-only 的 SQLite 探查工具: go run ./cmd/dumpdb /path/to/tts.db 输出: - 所有表 - schema_version - settings(敏感字段打码: api_key/auth_key/speaker_id 等) - voices(完整行,无打码 — 这是 ops 用) - journal_mode/page_size 等 pragmas 用法: 服务器 db 排查时,直接下 tts.db 下来用这个查, 不用 sqlite3 CLI,也不用写 SQL。 无依赖(用已有的 modernc.org/sqlite)。 未 push(收尾 commit,跟之前一起发)
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
// dumpdb 是个 ops 工具: dump tts.db 里的 settings + voices 表(明文打码)。
|
||||
//
|
||||
// 用法:
|
||||
// go run ./cmd/dumpdb /path/to/tts.db
|
||||
//
|
||||
// 输出 schema_version / settings(键值,敏感字段打码) / voices(完整行)。
|
||||
// 调试时方便:不依赖 sqlite3 CLI,不暴露明文 key/speaker ID。
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s <db-path>\n", os.Args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
dbPath := os.Args[1]
|
||||
|
||||
dsn := "file:" + dbPath + "?mode=ro&_pragma=query_only"
|
||||
conn, err := sql.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "open: %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 1. tables
|
||||
fmt.Println("=== Tables ===")
|
||||
rows, _ := conn.Query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
||||
for rows.Next() {
|
||||
var name string
|
||||
_ = rows.Scan(&name)
|
||||
fmt.Printf(" %s\n", name)
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
// 2. schema_version
|
||||
fmt.Println("\n=== schema_version ===")
|
||||
var ver int
|
||||
var ts string
|
||||
err = conn.QueryRow(`SELECT version, applied_at FROM schema_version LIMIT 1`).Scan(&ver, &ts)
|
||||
if err != nil {
|
||||
fmt.Printf(" (no row: %v)\n", err)
|
||||
} else {
|
||||
fmt.Printf(" version: %d, applied_at: %s\n", ver, ts)
|
||||
}
|
||||
|
||||
// 3. settings
|
||||
fmt.Println("\n=== settings ===")
|
||||
rows, err = conn.Query(`SELECT key, value FROM settings ORDER BY key`)
|
||||
if err != nil {
|
||||
fmt.Printf(" err: %v\n", err)
|
||||
} else {
|
||||
for rows.Next() {
|
||||
var k, v string
|
||||
_ = rows.Scan(&k, &v)
|
||||
if v == "" {
|
||||
fmt.Printf(" %-30s = (empty)\n", k)
|
||||
continue
|
||||
}
|
||||
lowK := k
|
||||
_ = lowK
|
||||
masked := v
|
||||
if isSensitive(k) {
|
||||
if len(v) <= 8 {
|
||||
masked = "****"
|
||||
} else {
|
||||
masked = v[:4] + "****" + v[len(v)-4:]
|
||||
}
|
||||
}
|
||||
fmt.Printf(" %-30s = %s\n", k, masked)
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
// 4. voices
|
||||
fmt.Println("\n=== voices ===")
|
||||
rows, err = conn.Query(`SELECT id, name, speaker, resource_id, model, language, description, enabled, created_at, updated_at FROM voices ORDER BY id`)
|
||||
if err != nil {
|
||||
fmt.Printf(" err: %v\n", err)
|
||||
} else {
|
||||
count := 0
|
||||
for rows.Next() {
|
||||
count++
|
||||
var id int64
|
||||
var name, speaker, resourceID, model, lang, desc, created, updated string
|
||||
var enabled int
|
||||
_ = rows.Scan(&id, &name, &speaker, &resourceID, &model, &lang, &desc, &enabled, &created, &updated)
|
||||
en := "true"
|
||||
if enabled == 0 {
|
||||
en = "false"
|
||||
}
|
||||
fmt.Printf(" [%d] name=%s\n speaker=%s\n resource_id=%s\n model=%s\n lang=%s enabled=%s\n created=%s updated=%s\n",
|
||||
id, name, speaker, resourceID, model, lang, en, created, updated)
|
||||
if desc != "" {
|
||||
fmt.Printf(" desc: %s\n", desc)
|
||||
}
|
||||
}
|
||||
fmt.Printf(" total: %d\n", count)
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
// 5. pragmas
|
||||
fmt.Println("\n=== Pragmas ===")
|
||||
for _, p := range []string{"journal_mode", "synchronous", "foreign_keys", "page_size", "page_count"} {
|
||||
var v string
|
||||
_ = conn.QueryRow("PRAGMA " + p).Scan(&v)
|
||||
fmt.Printf(" %-15s = %s\n", p, v)
|
||||
}
|
||||
}
|
||||
|
||||
func isSensitive(key string) bool {
|
||||
lowK := key
|
||||
// 简单 contains (不引 strings 包保持精简)
|
||||
markers := []string{"key", "token", "speaker"}
|
||||
for _, m := range markers {
|
||||
for i := 0; i+len(m) <= len(lowK); i++ {
|
||||
if lowK[i:i+len(m)] == m {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user