aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-23 20:54:50 -0800
committerFuwn <[email protected]>2026-01-23 20:54:50 -0800
commit4f8955fb5b9e05a5d0e2f4811c13926a6cbbe4b3 (patch)
tree4d1bcd871bb90e55f1aa11b16bc8763ac692af73
parentfeat: Add database maintenance with backup/reset modes and triggers (diff)
downloadkaze-4f8955fb5b9e05a5d0e2f4811c13926a6cbbe4b3.tar.xz
kaze-4f8955fb5b9e05a5d0e2f4811c13926a6cbbe4b3.zip
feat: Switch to libsql driver for Turso compatibility
-rw-r--r--cmd/kaze/main.go18
-rw-r--r--go.mod15
-rw-r--r--go.sum62
-rw-r--r--internal/config/config.go9
-rw-r--r--internal/storage/sqlite.go123
5 files changed, 105 insertions, 122 deletions
diff --git a/cmd/kaze/main.go b/cmd/kaze/main.go
index ee12ae8..aaed1fa 100644
--- a/cmd/kaze/main.go
+++ b/cmd/kaze/main.go
@@ -8,6 +8,7 @@ import (
"os"
"os/signal"
"path/filepath"
+ "strings"
"syscall"
"time"
@@ -24,6 +25,18 @@ var (
date = "unknown"
)
+func maskDatabaseURL(url string) string {
+ if strings.Contains(url, "authToken=") {
+ idx := strings.Index(url, "authToken=")
+ end := strings.Index(url[idx:], "&")
+ if end == -1 {
+ return url[:idx] + "authToken=***"
+ }
+ return url[:idx] + "authToken=***" + url[idx+end:]
+ }
+ return url
+}
+
func main() {
// Parse flags
configPath := flag.String("config", "config.yaml", "Path to configuration file")
@@ -68,14 +81,15 @@ func main() {
"groups", len(cfg.Groups),
"incidents", len(cfg.Incidents))
- store, err := storage.NewWithMaintenance(cfg.Storage.Path, cfg.Storage.HistoryDays, cfg.Storage.Maintenance)
+ dbURL := cfg.Storage.GetDatabaseURL()
+ store, err := storage.NewWithMaintenance(dbURL, cfg.Storage.HistoryDays, cfg.Storage.Maintenance)
if err != nil {
logger.Error("failed to initialize storage", "error", err)
os.Exit(1)
}
defer store.Close()
logger.Info("initialized storage",
- "path", cfg.Storage.Path,
+ "url", maskDatabaseURL(dbURL),
"history_days", cfg.Storage.HistoryDays,
"maintenance_mode", store.GetMaintenanceMode())
diff --git a/go.mod b/go.mod
index 1022bbd..cd1bef4 100644
--- a/go.mod
+++ b/go.mod
@@ -3,23 +3,18 @@ module github.com/Fuwn/kaze
go 1.24.3
require (
+ github.com/fsnotify/fsnotify v1.9.0
+ github.com/go-ping/ping v1.2.0
+ github.com/tursodatabase/go-libsql v0.0.0-20251219133454-43644db490ff
gopkg.in/yaml.v3 v3.0.1
- modernc.org/sqlite v1.44.1
)
require (
- github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/fsnotify/fsnotify v1.9.0 // indirect
- github.com/go-ping/ping v1.2.0 // indirect
+ github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/google/uuid v1.6.0 // indirect
- github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/ncruces/go-strftime v1.0.0 // indirect
- github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
- modernc.org/libc v1.67.6 // indirect
- modernc.org/mathutil v1.7.1 // indirect
- modernc.org/memory v1.11.0 // indirect
)
diff --git a/go.sum b/go.sum
index bca9c21..247f746 100644
--- a/go.sum
+++ b/go.sum
@@ -1,76 +1,38 @@
-github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
-github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
+github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ=
github.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
-github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
-github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
-github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
-github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
-github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
-github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM=
+github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/tursodatabase/go-libsql v0.0.0-20251219133454-43644db490ff h1:Hvxz9W8fWpSg9xkiq8/q+3cVJo+MmLMfkjdS/u4nWFY=
+github.com/tursodatabase/go-libsql v0.0.0-20251219133454-43644db490ff/go.mod h1:TjsB2miB8RW2Sse8sdxzVTdeGlx74GloD5zJYUC38d8=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
-golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
-golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
-golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
-modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
-modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
-modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
-modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
-modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
-modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
-modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
-modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
-modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
-modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
-modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
-modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
-modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
-modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
-modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
-modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
-modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
-modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
-modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
-modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
-modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
-modernc.org/sqlite v1.44.1 h1:qybx/rNpfQipX/t47OxbHmkkJuv2JWifCMH8SVUiDas=
-modernc.org/sqlite v1.44.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
-modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
-modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
-modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
-modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
diff --git a/internal/config/config.go b/internal/config/config.go
index 250827b..3ac03ea 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -74,10 +74,19 @@ type ServerConfig struct {
// StorageConfig contains database settings
type StorageConfig struct {
Path string `yaml:"path"`
+ URL string `yaml:"url,omitempty"` // libsql:// or file:// URL (overrides path, supports $ENV_VAR)
HistoryDays int `yaml:"history_days"`
Maintenance MaintenanceConfig `yaml:"maintenance,omitempty"`
}
+func (s *StorageConfig) GetDatabaseURL() string {
+ url := s.URL
+ if url == "" {
+ url = s.Path
+ }
+ return os.ExpandEnv(url)
+}
+
// MaintenanceConfig controls database maintenance/pruning behavior
type MaintenanceConfig struct {
// Mode: "never" (default), "backup" (rename with epoch suffix), "reset" (delete in place)
diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go
index db0ed52..5fcc9b3 100644
--- a/internal/storage/sqlite.go
+++ b/internal/storage/sqlite.go
@@ -8,7 +8,7 @@ import (
"time"
"github.com/Fuwn/kaze/internal/config"
- _ "modernc.org/sqlite"
+ _ "github.com/tursodatabase/go-libsql"
)
// Storage handles all database operations
@@ -107,22 +107,27 @@ func NewWithMaintenance(dbPath string, historyDays int, maintenanceCfg config.Ma
return s, nil
}
-func openDB(dbPath string) (*sql.DB, error) {
- dsn := fmt.Sprintf("%s?_txlock=immediate&_busy_timeout=5000&_journal_mode=WAL&_synchronous=NORMAL", dbPath)
+func openDB(dbURL string) (*sql.DB, error) {
+ isRemote := strings.HasPrefix(dbURL, "libsql://") || strings.HasPrefix(dbURL, "https://")
- db, err := sql.Open("sqlite", dsn)
+ dsn := dbURL
+ if !isRemote && !strings.HasPrefix(dbURL, "file:") {
+ dsn = "file:" + dbURL
+ }
+
+ if !isRemote {
+ dsn = dsn + "?_txlock=immediate&_busy_timeout=5000&_journal_mode=WAL&_synchronous=NORMAL"
+ }
+
+ db, err := sql.Open("libsql", dsn)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
- db.SetMaxOpenConns(1)
- db.SetMaxIdleConns(1)
- db.SetConnMaxLifetime(0)
-
- var journalMode string
- if err := db.QueryRow("PRAGMA journal_mode").Scan(&journalMode); err != nil {
- db.Close()
- return nil, fmt.Errorf("failed to check journal mode: %w", err)
+ if !isRemote {
+ db.SetMaxOpenConns(1)
+ db.SetMaxIdleConns(1)
+ db.SetConnMaxLifetime(0)
}
if _, err := db.Exec("PRAGMA foreign_keys=ON"); err != nil {
@@ -133,55 +138,53 @@ func openDB(dbPath string) (*sql.DB, error) {
return db, nil
}
-// migrate creates the database schema
func (s *Storage) migrate() error {
- schema := `
- CREATE TABLE IF NOT EXISTS check_results (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- monitor_name TEXT NOT NULL,
- timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
- status TEXT NOT NULL CHECK(status IN ('up', 'down', 'degraded')),
- response_time_ms INTEGER NOT NULL DEFAULT 0,
- status_code INTEGER DEFAULT 0,
- error_message TEXT,
- ssl_expiry DATETIME,
- ssl_days_left INTEGER DEFAULT 0
- );
-
- CREATE INDEX IF NOT EXISTS idx_check_results_monitor_time
- ON check_results(monitor_name, timestamp DESC);
-
- CREATE INDEX IF NOT EXISTS idx_check_results_timestamp
- ON check_results(timestamp);
-
- CREATE TABLE IF NOT EXISTS daily_stats (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- monitor_name TEXT NOT NULL,
- date DATE NOT NULL,
- success_count INTEGER NOT NULL DEFAULT 0,
- failure_count INTEGER NOT NULL DEFAULT 0,
- total_checks INTEGER NOT NULL DEFAULT 0,
- avg_response_ms REAL DEFAULT 0,
- uptime_percent REAL DEFAULT 0,
- UNIQUE(monitor_name, date)
- );
-
- CREATE INDEX IF NOT EXISTS idx_daily_stats_monitor_date
- ON daily_stats(monitor_name, date DESC);
-
- CREATE TABLE IF NOT EXISTS monitor_state (
- monitor_name TEXT PRIMARY KEY,
- current_status TEXT NOT NULL DEFAULT 'unknown',
- last_check DATETIME,
- last_response_time_ms INTEGER DEFAULT 0,
- last_error TEXT,
- ssl_expiry DATETIME,
- ssl_days_left INTEGER DEFAULT 0
- );
- `
-
- _, err := s.db.Exec(schema)
- return err
+ statements := []string{
+ `CREATE TABLE IF NOT EXISTS check_results (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ monitor_name TEXT NOT NULL,
+ timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ status TEXT NOT NULL CHECK(status IN ('up', 'down', 'degraded')),
+ response_time_ms INTEGER NOT NULL DEFAULT 0,
+ status_code INTEGER DEFAULT 0,
+ error_message TEXT,
+ ssl_expiry DATETIME,
+ ssl_days_left INTEGER DEFAULT 0
+ )`,
+ `CREATE INDEX IF NOT EXISTS idx_check_results_monitor_time
+ ON check_results(monitor_name, timestamp DESC)`,
+ `CREATE INDEX IF NOT EXISTS idx_check_results_timestamp
+ ON check_results(timestamp)`,
+ `CREATE TABLE IF NOT EXISTS daily_stats (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ monitor_name TEXT NOT NULL,
+ date DATE NOT NULL,
+ success_count INTEGER NOT NULL DEFAULT 0,
+ failure_count INTEGER NOT NULL DEFAULT 0,
+ total_checks INTEGER NOT NULL DEFAULT 0,
+ avg_response_ms REAL DEFAULT 0,
+ uptime_percent REAL DEFAULT 0,
+ UNIQUE(monitor_name, date)
+ )`,
+ `CREATE INDEX IF NOT EXISTS idx_daily_stats_monitor_date
+ ON daily_stats(monitor_name, date DESC)`,
+ `CREATE TABLE IF NOT EXISTS monitor_state (
+ monitor_name TEXT PRIMARY KEY,
+ current_status TEXT NOT NULL DEFAULT 'unknown',
+ last_check DATETIME,
+ last_response_time_ms INTEGER DEFAULT 0,
+ last_error TEXT,
+ ssl_expiry DATETIME,
+ ssl_days_left INTEGER DEFAULT 0
+ )`,
+ }
+
+ for _, stmt := range statements {
+ if _, err := s.db.Exec(stmt); err != nil {
+ return fmt.Errorf("migration failed: %w", err)
+ }
+ }
+ return nil
}
// SaveCheckResult saves a check result and updates monitor state