// Package store persists deployment progress and Slice-2 metadata in // SQLite, and log lines in append-only .log files. The hot path is // AppendEvent — agents emit a lot of these and the dashboard wants them // live. Slice 2 adds sandboxes, templates, environments, routes, and // port allocations on top of the deployment rows. package store import ( "database/sql" "os" "path/filepath" "sync" _ "modernc.org/sqlite" ) type Store struct { db *sql.DB dir string logs map[string]*os.File // deploymentID -> file mu sync.Mutex } func Open(dir string) (*Store, error) { if err := os.MkdirAll(dir, 0o755); err != nil { return nil, err } if err := os.MkdirAll(filepath.Join(dir, "logs"), 0o755); err != nil { return nil, err } db, err := sql.Open("sqlite", filepath.Join(dir, "sdp.db")) if err != nil { return nil, err } if _, err := db.Exec(schema); err != nil { return nil, err } return &Store{db: db, dir: dir, logs: make(map[string]*os.File)}, nil } const schema = ` CREATE TABLE IF NOT EXISTS deployments ( id TEXT PRIMARY KEY, sandbox_id TEXT, repository TEXT, branch TEXT, user TEXT, state TEXT, container_id TEXT, host_port INTEGER, started_at INTEGER, completed_at INTEGER ); CREATE TABLE IF NOT EXISTS progress ( deployment_id TEXT, stage TEXT, ok INTEGER, at INTEGER ); CREATE TABLE IF NOT EXISTS sandboxes ( id TEXT PRIMARY KEY, name TEXT UNIQUE NOT NULL, gateway_branch TEXT, gateway_env_id TEXT, gateway_host_port INTEGER, created_at INTEGER, updated_at INTEGER ); CREATE TABLE IF NOT EXISTS sandbox_services ( id TEXT PRIMARY KEY, sandbox_id TEXT NOT NULL, repo TEXT NOT NULL, branch TEXT, env_id TEXT, host_port INTEGER, use_ocp INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS templates ( id TEXT PRIMARY KEY, name TEXT UNIQUE NOT NULL, gateway_branch TEXT, created_at INTEGER, updated_at INTEGER ); CREATE TABLE IF NOT EXISTS template_services ( id TEXT PRIMARY KEY, template_id TEXT NOT NULL, repo TEXT NOT NULL, branch TEXT, env_id TEXT, host_port INTEGER, use_ocp INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS environments ( id TEXT PRIMARY KEY, name TEXT UNIQUE NOT NULL, values_json TEXT NOT NULL, created_at INTEGER, updated_at INTEGER ); CREATE TABLE IF NOT EXISTS routes ( id TEXT PRIMARY KEY, sandbox_id TEXT NOT NULL, key TEXT NOT NULL, value TEXT, target_ocp INTEGER NOT NULL DEFAULT 0 ); ` func (s *Store) Close() error { s.mu.Lock() defer s.mu.Unlock() for _, f := range s.logs { _ = f.Close() } return s.db.Close() }