This commit is contained in:
@@ -5,36 +5,75 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// DB wraps a database connection
|
||||
type DB struct {
|
||||
*sql.DB
|
||||
dbType string // "sqlite" or "postgres"
|
||||
}
|
||||
|
||||
// New creates a new database connection
|
||||
func New(dbPath string) (*DB, error) {
|
||||
// Ensure directory exists
|
||||
dir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("create db directory: %w", err)
|
||||
// dbConn can be:
|
||||
// - SQLite: file path (e.g., "/var/lib/atlas/atlas.db") or "sqlite:///path/to/db"
|
||||
// - PostgreSQL: connection string (e.g., "postgres://user:pass@host:port/dbname?sslmode=disable")
|
||||
func New(dbConn string) (*DB, error) {
|
||||
var (
|
||||
conn *sql.DB
|
||||
dbType string
|
||||
err error
|
||||
)
|
||||
|
||||
// Detect database type from connection string
|
||||
if strings.HasPrefix(dbConn, "postgres://") || strings.HasPrefix(dbConn, "postgresql://") {
|
||||
// PostgreSQL connection
|
||||
dbType = "postgres"
|
||||
conn, err = sql.Open("postgres", dbConn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open postgres database: %w", err)
|
||||
}
|
||||
// PostgreSQL connection pool settings
|
||||
conn.SetMaxOpenConns(25)
|
||||
conn.SetMaxIdleConns(5)
|
||||
conn.SetConnMaxLifetime(5 * time.Minute)
|
||||
} else {
|
||||
// SQLite connection (default or explicit)
|
||||
dbType = "sqlite"
|
||||
dbPath := dbConn
|
||||
// Remove sqlite:// prefix if present
|
||||
if strings.HasPrefix(dbPath, "sqlite://") {
|
||||
dbPath = strings.TrimPrefix(dbPath, "sqlite://")
|
||||
// Handle sqlite:///path/to/db format
|
||||
if strings.HasPrefix(dbPath, "///") {
|
||||
dbPath = dbPath[2:] // Remove one /
|
||||
} else if strings.HasPrefix(dbPath, "//") {
|
||||
dbPath = dbPath[1:] // Remove one /
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure directory exists for SQLite
|
||||
dir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("create db directory: %w", err)
|
||||
}
|
||||
|
||||
// Configure connection pool
|
||||
conn, err = sql.Open("sqlite", dbPath+"?_foreign_keys=1&_journal_mode=WAL")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open sqlite database: %w", err)
|
||||
}
|
||||
// SQLite connection pool settings (SQLite has different limits)
|
||||
conn.SetMaxOpenConns(1) // SQLite doesn't support multiple writers well
|
||||
conn.SetMaxIdleConns(1)
|
||||
conn.SetConnMaxLifetime(0) // Keep connections open
|
||||
}
|
||||
|
||||
// Configure connection pool
|
||||
conn, err := sql.Open("sqlite", dbPath+"?_foreign_keys=1&_journal_mode=WAL")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open database: %w", err)
|
||||
}
|
||||
|
||||
// Set connection pool settings for better performance
|
||||
conn.SetMaxOpenConns(25) // Maximum number of open connections
|
||||
conn.SetMaxIdleConns(5) // Maximum number of idle connections
|
||||
conn.SetConnMaxLifetime(5 * time.Minute) // Maximum connection lifetime
|
||||
|
||||
db := &DB{DB: conn}
|
||||
db := &DB{DB: conn, dbType: dbType}
|
||||
|
||||
// Test connection
|
||||
if err := db.Ping(); err != nil {
|
||||
@@ -51,114 +90,229 @@ func New(dbPath string) (*DB, error) {
|
||||
|
||||
// migrate runs database migrations
|
||||
func (db *DB) migrate() error {
|
||||
schema := `
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
active INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
var schema string
|
||||
|
||||
-- Audit logs table
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
actor TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
resource TEXT NOT NULL,
|
||||
result TEXT NOT NULL,
|
||||
message TEXT,
|
||||
ip TEXT,
|
||||
user_agent TEXT,
|
||||
timestamp TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_logs(actor);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_resource ON audit_logs(resource);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_logs(timestamp);
|
||||
if db.dbType == "postgres" {
|
||||
// PostgreSQL schema
|
||||
schema = `
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
username VARCHAR(255) UNIQUE NOT NULL,
|
||||
email VARCHAR(255),
|
||||
password_hash TEXT NOT NULL,
|
||||
role VARCHAR(50) NOT NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- SMB shares table
|
||||
CREATE TABLE IF NOT EXISTS smb_shares (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
dataset TEXT NOT NULL,
|
||||
description TEXT,
|
||||
read_only INTEGER NOT NULL DEFAULT 0,
|
||||
guest_ok INTEGER NOT NULL DEFAULT 0,
|
||||
enabled INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
-- Audit logs table
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
actor VARCHAR(255) NOT NULL,
|
||||
action VARCHAR(100) NOT NULL,
|
||||
resource VARCHAR(255) NOT NULL,
|
||||
result VARCHAR(50) NOT NULL,
|
||||
message TEXT,
|
||||
ip VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_logs(actor);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_resource ON audit_logs(resource);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_logs(timestamp);
|
||||
|
||||
-- SMB share valid users (many-to-many)
|
||||
CREATE TABLE IF NOT EXISTS smb_share_users (
|
||||
share_id TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
PRIMARY KEY (share_id, username),
|
||||
FOREIGN KEY (share_id) REFERENCES smb_shares(id) ON DELETE CASCADE
|
||||
);
|
||||
-- SMB shares table
|
||||
CREATE TABLE IF NOT EXISTS smb_shares (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
name VARCHAR(255) UNIQUE NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
dataset VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
read_only BOOLEAN NOT NULL DEFAULT false,
|
||||
guest_ok BOOLEAN NOT NULL DEFAULT false,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true
|
||||
);
|
||||
|
||||
-- NFS exports table
|
||||
CREATE TABLE IF NOT EXISTS nfs_exports (
|
||||
id TEXT PRIMARY KEY,
|
||||
path TEXT UNIQUE NOT NULL,
|
||||
dataset TEXT NOT NULL,
|
||||
read_only INTEGER NOT NULL DEFAULT 0,
|
||||
root_squash INTEGER NOT NULL DEFAULT 1,
|
||||
enabled INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
-- SMB share valid users (many-to-many)
|
||||
CREATE TABLE IF NOT EXISTS smb_share_users (
|
||||
share_id VARCHAR(255) NOT NULL,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (share_id, username),
|
||||
FOREIGN KEY (share_id) REFERENCES smb_shares(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- NFS export clients (many-to-many)
|
||||
CREATE TABLE IF NOT EXISTS nfs_export_clients (
|
||||
export_id TEXT NOT NULL,
|
||||
client TEXT NOT NULL,
|
||||
PRIMARY KEY (export_id, client),
|
||||
FOREIGN KEY (export_id) REFERENCES nfs_exports(id) ON DELETE CASCADE
|
||||
);
|
||||
-- NFS exports table
|
||||
CREATE TABLE IF NOT EXISTS nfs_exports (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
path TEXT UNIQUE NOT NULL,
|
||||
dataset VARCHAR(255) NOT NULL,
|
||||
read_only BOOLEAN NOT NULL DEFAULT false,
|
||||
root_squash BOOLEAN NOT NULL DEFAULT true,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true
|
||||
);
|
||||
|
||||
-- iSCSI targets table
|
||||
CREATE TABLE IF NOT EXISTS iscsi_targets (
|
||||
id TEXT PRIMARY KEY,
|
||||
iqn TEXT UNIQUE NOT NULL,
|
||||
enabled INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
-- NFS export clients (many-to-many)
|
||||
CREATE TABLE IF NOT EXISTS nfs_export_clients (
|
||||
export_id VARCHAR(255) NOT NULL,
|
||||
client VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (export_id, client),
|
||||
FOREIGN KEY (export_id) REFERENCES nfs_exports(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- iSCSI target initiators (many-to-many)
|
||||
CREATE TABLE IF NOT EXISTS iscsi_target_initiators (
|
||||
target_id TEXT NOT NULL,
|
||||
initiator TEXT NOT NULL,
|
||||
PRIMARY KEY (target_id, initiator),
|
||||
FOREIGN KEY (target_id) REFERENCES iscsi_targets(id) ON DELETE CASCADE
|
||||
);
|
||||
-- iSCSI targets table
|
||||
CREATE TABLE IF NOT EXISTS iscsi_targets (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
iqn VARCHAR(255) UNIQUE NOT NULL,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true
|
||||
);
|
||||
|
||||
-- iSCSI LUNs table
|
||||
CREATE TABLE IF NOT EXISTS iscsi_luns (
|
||||
target_id TEXT NOT NULL,
|
||||
lun_id INTEGER NOT NULL,
|
||||
zvol TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
backend TEXT NOT NULL DEFAULT 'zvol',
|
||||
PRIMARY KEY (target_id, lun_id),
|
||||
FOREIGN KEY (target_id) REFERENCES iscsi_targets(id) ON DELETE CASCADE
|
||||
);
|
||||
-- iSCSI target initiators (many-to-many)
|
||||
CREATE TABLE IF NOT EXISTS iscsi_target_initiators (
|
||||
target_id VARCHAR(255) NOT NULL,
|
||||
initiator VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (target_id, initiator),
|
||||
FOREIGN KEY (target_id) REFERENCES iscsi_targets(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Snapshot policies table
|
||||
CREATE TABLE IF NOT EXISTS snapshot_policies (
|
||||
id TEXT PRIMARY KEY,
|
||||
dataset TEXT NOT NULL,
|
||||
schedule_type TEXT NOT NULL,
|
||||
schedule_value TEXT,
|
||||
retention_count INTEGER,
|
||||
retention_days INTEGER,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_snapshot_policy_dataset ON snapshot_policies(dataset);
|
||||
`
|
||||
-- iSCSI LUNs table
|
||||
CREATE TABLE IF NOT EXISTS iscsi_luns (
|
||||
target_id VARCHAR(255) NOT NULL,
|
||||
lun_id INTEGER NOT NULL,
|
||||
zvol VARCHAR(255) NOT NULL,
|
||||
size BIGINT NOT NULL,
|
||||
backend VARCHAR(50) NOT NULL DEFAULT 'zvol',
|
||||
PRIMARY KEY (target_id, lun_id),
|
||||
FOREIGN KEY (target_id) REFERENCES iscsi_targets(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Snapshot policies table
|
||||
CREATE TABLE IF NOT EXISTS snapshot_policies (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
dataset VARCHAR(255) NOT NULL,
|
||||
schedule_type VARCHAR(50) NOT NULL,
|
||||
schedule_value VARCHAR(255),
|
||||
retention_count INTEGER,
|
||||
retention_days INTEGER,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_snapshot_policy_dataset ON snapshot_policies(dataset);
|
||||
`
|
||||
} else {
|
||||
// SQLite schema (original)
|
||||
schema = `
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
active INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Audit logs table
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
actor TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
resource TEXT NOT NULL,
|
||||
result TEXT NOT NULL,
|
||||
message TEXT,
|
||||
ip TEXT,
|
||||
user_agent TEXT,
|
||||
timestamp TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_logs(actor);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_resource ON audit_logs(resource);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_logs(timestamp);
|
||||
|
||||
-- SMB shares table
|
||||
CREATE TABLE IF NOT EXISTS smb_shares (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
dataset TEXT NOT NULL,
|
||||
description TEXT,
|
||||
read_only INTEGER NOT NULL DEFAULT 0,
|
||||
guest_ok INTEGER NOT NULL DEFAULT 0,
|
||||
enabled INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
-- SMB share valid users (many-to-many)
|
||||
CREATE TABLE IF NOT EXISTS smb_share_users (
|
||||
share_id TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
PRIMARY KEY (share_id, username),
|
||||
FOREIGN KEY (share_id) REFERENCES smb_shares(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- NFS exports table
|
||||
CREATE TABLE IF NOT EXISTS nfs_exports (
|
||||
id TEXT PRIMARY KEY,
|
||||
path TEXT UNIQUE NOT NULL,
|
||||
dataset TEXT NOT NULL,
|
||||
read_only INTEGER NOT NULL DEFAULT 0,
|
||||
root_squash INTEGER NOT NULL DEFAULT 1,
|
||||
enabled INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
-- NFS export clients (many-to-many)
|
||||
CREATE TABLE IF NOT EXISTS nfs_export_clients (
|
||||
export_id TEXT NOT NULL,
|
||||
client TEXT NOT NULL,
|
||||
PRIMARY KEY (export_id, client),
|
||||
FOREIGN KEY (export_id) REFERENCES nfs_exports(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- iSCSI targets table
|
||||
CREATE TABLE IF NOT EXISTS iscsi_targets (
|
||||
id TEXT PRIMARY KEY,
|
||||
iqn TEXT UNIQUE NOT NULL,
|
||||
enabled INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
-- iSCSI target initiators (many-to-many)
|
||||
CREATE TABLE IF NOT EXISTS iscsi_target_initiators (
|
||||
target_id TEXT NOT NULL,
|
||||
initiator TEXT NOT NULL,
|
||||
PRIMARY KEY (target_id, initiator),
|
||||
FOREIGN KEY (target_id) REFERENCES iscsi_targets(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- iSCSI LUNs table
|
||||
CREATE TABLE IF NOT EXISTS iscsi_luns (
|
||||
target_id TEXT NOT NULL,
|
||||
lun_id INTEGER NOT NULL,
|
||||
zvol TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
backend TEXT NOT NULL DEFAULT 'zvol',
|
||||
PRIMARY KEY (target_id, lun_id),
|
||||
FOREIGN KEY (target_id) REFERENCES iscsi_targets(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Snapshot policies table
|
||||
CREATE TABLE IF NOT EXISTS snapshot_policies (
|
||||
id TEXT PRIMARY KEY,
|
||||
dataset TEXT NOT NULL,
|
||||
schedule_type TEXT NOT NULL,
|
||||
schedule_value TEXT,
|
||||
retention_count INTEGER,
|
||||
retention_days INTEGER,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_snapshot_policy_dataset ON snapshot_policies(dataset);
|
||||
`
|
||||
}
|
||||
|
||||
if _, err := db.Exec(schema); err != nil {
|
||||
return fmt.Errorf("create schema: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user