Files
2026-05-16 19:19:39 +08:00

187 lines
5.1 KiB
Go

package main
import (
"database/sql"
"encoding/json"
"log"
"os"
"path/filepath"
"strings"
_ "modernc.org/sqlite"
)
// DB is the global SQLite connection shared by all handlers.
var DB *sql.DB
const schema = `
CREATE TABLE IF NOT EXISTS ssh_profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
alias TEXT NOT NULL,
host TEXT NOT NULL,
port INTEGER NOT NULL DEFAULT 22,
username TEXT NOT NULL,
password TEXT NOT NULL DEFAULT '',
private_key TEXT NOT NULL DEFAULT '',
passphrase TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS commands (
id INTEGER PRIMARY KEY AUTOINCREMENT,
alias TEXT NOT NULL,
command TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS scripts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
content TEXT NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
`
// initDB opens the SQLite database, applies PRAGMAs and creates tables.
func initDB() error {
if err := os.MkdirAll(dataBasePath(), 0o750); err != nil {
return err
}
db, err := sql.Open("sqlite", dbPath())
if err != nil {
return err
}
// Single writer; multiple readers are fine via WAL.
db.SetMaxOpenConns(1)
if _, err := db.Exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;`); err != nil {
return err
}
if _, err := db.Exec(schema); err != nil {
return err
}
DB = db
log.Printf("SQLite database ready: %s", dbPath())
return nil
}
// ─── One-time migration from legacy file storage ─────────────────────────────
// migrateFromFiles reads legacy JSON/text files and inserts their data into
// SQLite (INSERT OR IGNORE so duplicates are skipped). After migration each
// source file is renamed to *.migrated so the routine is not repeated.
func migrateFromFiles() {
migrateSSHProfiles()
migrateCommands()
migrateScripts()
}
func migrateSSHProfiles() {
dir := filepath.Join(dataBasePath(), "ssh")
entries, err := os.ReadDir(dir)
if err != nil {
return // directory doesn't exist — nothing to migrate
}
migrated := 0
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".json") {
continue
}
path := filepath.Join(dir, e.Name())
raw, err := os.ReadFile(path)
if err != nil {
log.Printf("migrate ssh: read %s: %v", path, err)
continue
}
var p SSHProfile
if err := json.Unmarshal(raw, &p); err != nil {
log.Printf("migrate ssh: parse %s: %v", path, err)
continue
}
name := strings.TrimSuffix(e.Name(), ".json")
if p.Port == 0 {
p.Port = 22
}
_, err = DB.Exec(
`INSERT OR IGNORE INTO ssh_profiles (name,alias,host,port,username,password,private_key,passphrase)
VALUES (?,?,?,?,?,?,?,?)`,
name, p.Alias, p.Host, p.Port, p.Username, p.Password, p.PrivateKey, p.Passphrase,
)
if err != nil {
log.Printf("migrate ssh: insert %s: %v", name, err)
continue
}
_ = os.Rename(path, path+".migrated")
migrated++
}
if migrated > 0 {
log.Printf("migrate: imported %d SSH profile(s) from %s", migrated, dir)
}
}
func migrateCommands() {
path := filepath.Join(dataBasePath(), "command", "command.json")
raw, err := os.ReadFile(path)
if err != nil {
return
}
var cmds []Command
if err := json.Unmarshal(raw, &cmds); err != nil {
log.Printf("migrate commands: parse %s: %v", path, err)
return
}
// Check if commands table is already populated to avoid duplicating on restart.
var count int
_ = DB.QueryRow(`SELECT COUNT(*) FROM commands`).Scan(&count)
if count > 0 {
log.Printf("migrate commands: table not empty, skipping file migration")
_ = os.Rename(path, path+".migrated")
return
}
for i, cmd := range cmds {
if _, err := DB.Exec(
`INSERT INTO commands (alias, command, sort_order) VALUES (?,?,?)`,
cmd.Alias, cmd.Command, i,
); err != nil {
log.Printf("migrate commands: insert [%d]: %v", i, err)
}
}
_ = os.Rename(path, path+".migrated")
log.Printf("migrate: imported %d command(s) from %s", len(cmds), path)
}
func migrateScripts() {
dir := filepath.Join(dataBasePath(), "script")
entries, err := os.ReadDir(dir)
if err != nil {
return
}
migrated := 0
for _, e := range entries {
if e.IsDir() {
continue
}
path := filepath.Join(dir, e.Name())
content, err := os.ReadFile(path)
if err != nil {
log.Printf("migrate scripts: read %s: %v", path, err)
continue
}
if _, err := DB.Exec(
`INSERT OR IGNORE INTO scripts (name, content) VALUES (?,?)`,
e.Name(), string(content),
); err != nil {
log.Printf("migrate scripts: insert %s: %v", e.Name(), err)
continue
}
_ = os.Rename(path, path+".migrated")
migrated++
}
if migrated > 0 {
log.Printf("migrate: imported %d script(s) from %s", migrated, dir)
}
}