Files
Achmad 55d7705c63 Slice 2: real auth, agent-mediated repo/branch listing, deployment list from SQLite
- protocol: add RepoInfo, RouteOverride; add HostPort, SandboxID to DeployRequest.
- ws hub: add CallAgent for sync request/response RPCs over the agent WS,
  and DeliverAgentReply to route {op:reply} frames back to the caller.
  UnregisterAgent now also fails any pending RPCs so callers don't hang.
- agent-micro: new op handlers list_repos, list_branches, probe.
  Wire protocol.Event frames use json.RawMessage so each op decodes
  its own data shape.
- agent-gateway: same op handlers (list_repos/list_branches/probe) plus
  push_routes, which the gateway uses to rewrite the api-gateway
  config.php. Detailed in a later commit.
- control-plane login: validateViaAgent now calls CallAgent('probe')
  against the gateway agent (git ls-remote), replacing the
  accept-any-creds stub.
- control-plane repos: handleListRepos and handleListBranches forward
  to the agents via list_repos / list_branches RPCs, replacing the
  hardcoded fixtures.
- control-plane deployments: split into its own file. handleListDeployments
  reads from SQLite (was hardcoded []). handleCreateDeployment now
  supports sandbox-scoped deploys with a host port + env merge.
  handleStopDeployment looks up the node from the deployment row.
- store: split into store.go + deployments.go. The Deployment type
  adds sandboxId, containerId, hostPort. StartDeploymentInSandbox,
  SetContainerID, ListDeployments, GetDeployment, LatestDeploymentBySandboxService
  are new.
- store_test.go: round-trips every Slice-2 path (env, sandbox,
  template, clone, routes, deployment).
- .gitignore: track bin/ — the build runs on a separate Linux box
  with the golang:1.24 toolchain, and the binaries are SCPed from
  there to the company VMs (92 / 186). The VMs have no internet.
- Tracked bin/{control-plane,agent-micro,agent-gateway}.
2026-06-24 03:58:53 +00:00

122 lines
2.6 KiB
Go

// 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()
}