- api/openapi.yaml converted 3.1 → 3.0.3 (oapi-codegen/kin-openapi supports 3.0; nullable syntax + example keywords), still redocly-clean - oapi-codegen (v2.4.1, strict server + chi) generates internal/httpapi/gen from the spec; `make generate` wired - internal/httpapi: chi router, /healthz (unauthenticated, SG18), RFC 9457 problem+json mapping from domain sentinels (SG11), 5xx detail logged server-side only, request logging with request IDs, graceful shutdown (SG4), interim static bearer auth (constant-time; dev-open when no token; OIDC JWT still to come in Phase 2) - Implemented: listEntities (type filter walks the hierarchy, keyset pagination), getEntity (UUID or slug, ETag), getEntityRelations, getBlastRadius, getGraph (nodes+edges for UIs), getOntology, listSignals, getFleetHealth, exportSeeds. Remaining 38 ops return 501 problem+json stubs (compiler-enforced interface completeness) - `oikos api` role live: migrate-on-start, serves :8090 - 15 API integration tests (auth, pagination, hierarchy filter, ETag, 404/501 problem shapes, graph, export) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Config holds all runtime configuration for an Oikos role.
|
|
// Hierarchy: compiled defaults → config file → env vars → Infisical (secrets only).
|
|
type Config struct {
|
|
// Database
|
|
DatabaseURL string // postgres://user:pass@host:5432/oikos?sslmode=disable
|
|
|
|
// API
|
|
APIListen string // :8090
|
|
APIEnv string // dev, prod
|
|
|
|
// Auth (interim Phase 2: static bearer tokens; OIDC JWT later)
|
|
APIToken string // operator/CI bearer token for the REST API
|
|
MCPBearerToken string // shared secret for Hermes→API MCP calls
|
|
|
|
// Observability
|
|
Debug bool // verbose logging, probe payloads, SQL
|
|
|
|
// Seeds directory (for ingest/export)
|
|
SeedsDir string
|
|
|
|
// Migrations directory (embedded at build time, but path for fallback)
|
|
MigrationsDir string
|
|
}
|
|
|
|
// Default returns a Config with compiled defaults.
|
|
func Default() Config {
|
|
return Config{
|
|
DatabaseURL: "postgres://oikos:oikos@localhost:5432/oikos?sslmode=disable",
|
|
APIListen: ":8090",
|
|
APIEnv: "dev",
|
|
SeedsDir: "seeds",
|
|
MigrationsDir: "migrations",
|
|
}
|
|
}
|
|
|
|
// FromEnv loads config from environment variables, overlaying defaults.
|
|
func FromEnv() Config {
|
|
c := Default()
|
|
|
|
if v := os.Getenv("OIKOS_DATABASE_URL"); v != "" {
|
|
c.DatabaseURL = v
|
|
}
|
|
if v := os.Getenv("OIKOS_API_LISTEN"); v != "" {
|
|
c.APIListen = v
|
|
}
|
|
if v := os.Getenv("OIKOS_ENV"); v != "" {
|
|
c.APIEnv = v
|
|
}
|
|
if v := os.Getenv("OIKOS_API_TOKEN"); v != "" {
|
|
c.APIToken = v
|
|
}
|
|
if v := os.Getenv("OIKOS_MCP_BEARER_TOKEN"); v != "" {
|
|
c.MCPBearerToken = v
|
|
}
|
|
if v := os.Getenv("OIKOS_SEEDS_DIR"); v != "" {
|
|
c.SeedsDir = v
|
|
}
|
|
c.Debug = os.Getenv("OIKOS_DEBUG") == "true" || os.Getenv("OIKOS_DEBUG") == "1"
|
|
|
|
return c
|
|
}
|
|
|
|
// redactedDBURL masks credentials in a postgres:// URL.
|
|
func (c Config) redactedDBURL() string {
|
|
dbURL := c.DatabaseURL
|
|
if i := strings.Index(dbURL, "@"); i >= 0 {
|
|
if j := strings.Index(dbURL, "://"); j >= 0 && j < i {
|
|
dbURL = dbURL[:j+3] + "***" + dbURL[i:]
|
|
}
|
|
}
|
|
return dbURL
|
|
}
|
|
|
|
// String returns a human-safe representation (secrets redacted).
|
|
func (c Config) String() string {
|
|
token := ""
|
|
if c.MCPBearerToken != "" {
|
|
token = "***"
|
|
}
|
|
return fmt.Sprintf("Config{DB=%s Listen=%s Env=%s Debug=%v MCPToken=%s SeedsDir=%s}",
|
|
c.redactedDBURL(), c.APIListen, c.APIEnv, c.Debug, token, c.SeedsDir)
|
|
}
|
|
|
|
// LogValue implements slog.LogValuer so structured handlers (JSON) never
|
|
// serialize raw secrets — without this, slog marshals struct fields
|
|
// directly and String() is bypassed.
|
|
func (c Config) LogValue() slog.Value {
|
|
token := ""
|
|
if c.MCPBearerToken != "" {
|
|
token = "***"
|
|
}
|
|
return slog.GroupValue(
|
|
slog.String("db", c.redactedDBURL()),
|
|
slog.String("listen", c.APIListen),
|
|
slog.String("env", c.APIEnv),
|
|
slog.Bool("debug", c.Debug),
|
|
slog.String("mcp_token", token),
|
|
slog.String("seeds_dir", c.SeedsDir),
|
|
)
|
|
}
|