summaryrefslogtreecommitdiff
path: root/internal/storage/db.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-16 08:43:55 +0300
committerPaul Buetow <paul@buetow.org>2026-04-16 08:43:55 +0300
commitedeffd05dc62c4c3d747cc41067bf4e1814f300a (patch)
tree11881c2f5a40013b3885e4a6b51b8a4f00e56f80 /internal/storage/db.go
parent71211a54519e13c9ba5ba928352fa4fef001240b (diff)
Add excluded_hosts feature: store in SQLite, expose CLI subcommands
Adds an excluded_host table to the SQLite schema and three new CLI subcommands (exclude, unexclude, list-excluded) so operators can mark hosts that are no longer expected to send updates. The IsExcludedHost and LoadExcludedHosts storage helpers are ready for the Prometheus alerting endpoint (task d4). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/storage/db.go')
-rw-r--r--internal/storage/db.go68
1 files changed, 68 insertions, 0 deletions
diff --git a/internal/storage/db.go b/internal/storage/db.go
index edd5a93..d500509 100644
--- a/internal/storage/db.go
+++ b/internal/storage/db.go
@@ -26,6 +26,11 @@ CREATE INDEX IF NOT EXISTS idx_record_host ON record(host);
CREATE INDEX IF NOT EXISTS idx_record_os ON record(os);
CREATE INDEX IF NOT EXISTS idx_record_os_kernel_name ON record(os_kernel_name);
CREATE INDEX IF NOT EXISTS idx_record_os_kernel_major ON record(os_kernel_major);
+CREATE TABLE IF NOT EXISTS excluded_host (
+ host TEXT NOT NULL PRIMARY KEY,
+ reason TEXT NOT NULL DEFAULT '',
+ excluded_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
+);
`
// Record is one uptimed boot row stored in the record table.
@@ -136,6 +141,69 @@ func LoadRecords(ctx context.Context, db *sql.DB) ([]Record, error) {
return out, nil
}
+// ExcludedHost holds an entry from the excluded_host table.
+type ExcludedHost struct {
+ Host string
+ Reason string
+ ExcludedAt int64
+}
+
+// AddExcludedHost inserts or replaces a host in the excluded_host table.
+func AddExcludedHost(ctx context.Context, db *sql.DB, host, reason string) error {
+ _, err := db.ExecContext(ctx,
+ "INSERT OR REPLACE INTO excluded_host (host, reason) VALUES (?, ?)",
+ host, reason)
+ if err != nil {
+ return fmt.Errorf("add excluded host: %w", err)
+ }
+ return nil
+}
+
+// RemoveExcludedHost removes a host from the excluded_host table.
+func RemoveExcludedHost(ctx context.Context, db *sql.DB, host string) error {
+ _, err := db.ExecContext(ctx, "DELETE FROM excluded_host WHERE host = ?", host)
+ if err != nil {
+ return fmt.Errorf("remove excluded host: %w", err)
+ }
+ return nil
+}
+
+// LoadExcludedHosts returns all rows from the excluded_host table.
+func LoadExcludedHosts(ctx context.Context, db *sql.DB) ([]ExcludedHost, error) {
+ rows, err := db.QueryContext(ctx, "SELECT host, reason, excluded_at FROM excluded_host ORDER BY host")
+ if err != nil {
+ return nil, fmt.Errorf("query excluded hosts: %w", err)
+ }
+ defer rows.Close()
+ var out []ExcludedHost
+ for rows.Next() {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+ var e ExcludedHost
+ if err := rows.Scan(&e.Host, &e.Reason, &e.ExcludedAt); err != nil {
+ return nil, fmt.Errorf("scan excluded host: %w", err)
+ }
+ out = append(out, e)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, fmt.Errorf("rows excluded hosts: %w", err)
+ }
+ return out, nil
+}
+
+// IsExcludedHost reports whether a host is in the excluded_host table.
+func IsExcludedHost(ctx context.Context, db *sql.DB, host string) (bool, error) {
+ var count int
+ err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM excluded_host WHERE host = ?", host).Scan(&count)
+ if err != nil {
+ return false, fmt.Errorf("check excluded host: %w", err)
+ }
+ return count > 0, nil
+}
+
func importFile(ctx context.Context, insert *sql.Stmt, fsys fs.FS, relPath, host string) error {
f, err := fsys.Open(relPath)
if err != nil {