api

package
v0.2.0-beta.19 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: Apache-2.0 Imports: 171 Imported by: 0

Documentation ¶

Overview ¶

Package api implements the HTTP API the web frontend (1.10) and, later, the MCP layer both build on. Scope for this pass: GET /api/v1/brand, apps CRUD, deploy trigger and deploy history, single-admin-user session auth. No teams, no RBAC: explicitly out of scope until Phase 4.

Routing uses the standard library's Go 1.22+ pattern-based http.ServeMux ("GET /api/v1/apps/{name}") rather than a router framework like Chi, per the project's rule against pulling in a heavy framework; the one thing a framework would earn its weight on here is auth middleware, and requireAuth below is a plain http.HandlerFunc-wrapping closure that stdlib's mux composes with directly, so there is no ergonomics gap stdlib doesn't already close.

"App" here is layered directly on store.DesiredService (internal/store/service.go), the closest existing resource, rather than a new, speculative domain type. That surfaces real gaps instead of papering over them with a fuller model this package doesn't actually need to build yet:

  • No replicas or strategy fields on an app: internal/spec's app.yaml Service has them, store.DesiredService doesn't yet, so this API can't expose what the store can't hold. Adding them is a store-schema and deploy-pipeline change, not something this package should invent on its own. Domains closed once that column was added: appResource now carries Domains too.
  • Deploy trigger (deploys.go) only updates desired_services.image; it doesn't build anything. internal/build and internal/deploy already exist and do that, but they're owned by a different concurrent session as this package was written, so this endpoint takes an already-built image tag as input, the same mechanism used for rollback, run forward instead of backward. POST /api/v1/apps/{name}/builds (builds.go, handleTriggerBuild) closes this gap: it invokes the same internal/deploy.Pipeline the git webhook receiver uses, given a git source (repo_url/ref) in the request body, since no app has a stored git/build config anywhere in this codebase yet (see specServiceFromDesired's own doc comment for what that means for fidelity versus the original app.yaml).
  • Deploy history (deploys.go) returns the latest reconcile condition per (controller, condition type) pair, which is genuinely all internal/store.UpsertConditions persists today, not a row-per- deploy-attempt log. A real deploy history table is a store-schema addition this package deliberately didn't invent speculatively.

cmd/levelrail/main.go's reconcile engine closed the gap noted above in an earlier draft of this comment: it now derives a dynamic controller set from the store every pass (reconcile.Engine.Source), so a deploy triggered through this API does reconcile on the next pass.

Secrets: PUT /api/v1/apps/{name}/secrets/{key} sets a value, encrypted at rest via internal/secrets.Manager. Deliberately set-only, no GET: this package never decrypts a value for a response body, only internal/reconcile/application does, immediately before container creation. Available only when the control plane was started with a master key (WithSecretSetter); without one, the route returns 501.

Auth foundation ("Dashboard & auth"): POST /api/v1/auth/register is the interactive first-run counterpart to BootstrapAdmin's env-var path, gated on "no admin row exists yet" at both the route and the mutation layer. Session auth stays exactly what this package was scoped as (single admin user, no teams, no RBAC); API tokens (POST/GET /api/v1/auth/tokens, DELETE .../{id}) are a separate, additive credential type for non-interactive callers (a future CLI, an MCP server), scoped to abilities (abilities.go: read, read:sensitive, write, deploy, root) checked fresh on every call by requireAbility, never a cached decision. Token management itself is session-only via requireAuth: a token can never mint or revoke another token on its own behalf. This shape (not theauth-go, not an all-or-nothing key) was a deliberate choice.

Index ¶

Constants ¶

View Source
const (
	AbilityRead           = "read"
	AbilityReadSensitive  = "read:sensitive"
	AbilityWrite          = "write"
	AbilityWriteSensitive = "write:sensitive"
	AbilityDeploy         = "deploy"
	AbilityRoot           = "root"
	// AbilitySignInApprove lets a token reveal its owner's sign-in codes and
	// decide new-browser approvals. Root never implies it.
	AbilitySignInApprove = "signin:approve"
)

Ability strings an api_tokens row can be scoped to, adopted from Coolify's own model: a small, legible permission surface an MCP-issued token can be provably scoped to at the token layer itself, not just by convention in what a caller chooses to call. AbilityRoot is exclusive of the rest (checked at mint time, see validateAbilities), not additive with them.

View Source
const (
	AIAssistantTokenName = "AI Assistant (internal)"
	AIAssistantAgentName = "ai-assistant"
)

AIAssistantTokenName and AIAssistantAgentName identify the internal token the in-app assistant holds.

View Source
const (
	HealthScoreStatusPass = "pass"
	HealthScoreStatusWarn = "warn"
	HealthScoreStatusFail = "fail"
)

Health score statuses. One shared set of constants, referenced by every category computation below and by the CLI's own rendering, so "pass"/"warn"/"fail" is never retyped as a bare string literal in two places that could silently drift.

View Source
const (
	ClientKindCLI       = "cli"
	ClientKindDashboard = "dashboard"
	ClientKindMCP       = "mcp"
	ClientKindAPI       = "api"
	// ClientKindSystem is written directly by internal/ingress's
	// SQLiteStorage for a certificate issuance/renewal it detects, not
	// derived here: there is no User-Agent, because there is no request.
	ClientKindSystem = "system"
)

Client kind values audit_log.client_kind (migrations/0077) is normalized into: which surface actually made the request, not just which auth method it used (store.AuditEntry.ActorType already covers session-vs-token).

View Source
const (
	OutcomeInProgress = "in_progress"
	OutcomeHealthy    = "healthy"
	OutcomeFailed     = "failed"
	OutcomeCanceled   = "canceled"
	OutcomeSuperseded = "superseded"
	OutcomeBlocked    = "blocked"
)

Deploy outcomes reported by GET /apps/{name}/deploys/{deployId}.

View Source
const (
	RoleAdmin    = "admin"
	RoleOperator = "operator"
	RoleViewer   = "viewer"
)

Curated role names, roles's own Name values.

View Source
const (
	TopologyKindApp      = "app"
	TopologyKindDatabase = "database"
	TopologyKindVolume   = "volume"

	TopologyEdgeDatabaseBinding = "database_binding"
	TopologyEdgeDependsOn       = "depends_on"
	TopologyEdgeVolume          = "volume_attachment"
	TopologyEdgeEgressAllow     = "egress_allow"
)

Topology node/edge kinds: the vocabulary GET /api/v1/projects/{id}/topology (topology.go) reports, and web/src/routes/projects/$id/topology.tsx renders. An "app" node is one store.DesiredService, the same unit GET /api/v1/apps already calls an app (appListResource); multi-service apps (store.App) show up as one node per member service, not one collapsed node, since depends_on and database bindings are declared per service, not per App row.

View Source
const ACMEStagingDirectoryURL = "https://acme-staging-v02.api.letsencrypt.org/directory"

ACMEStagingDirectoryURL is Let's Encrypt's staging CA: no rate limits that matter, certificates browsers do not trust.

View Source
const AgentClientHeader = "X-Agent-Client"

AgentClientHeader carries the MCP client name and version an agent-side server reports for the request. It is self-reported and informational.

View Source
const EnvGitStatusEnabled = "APP_GIT_STATUS_ENABLED"

EnvGitStatusEnabled is the server kill switch for posting commit statuses and deployments back to git forges. Unset means on.

View Source
const ExperimentalDisabledCode = "experimental_feature_disabled"

ExperimentalDisabledCode is the error code returned for a gated route.

View Source
const SetupTokenFilename = "setup-token"

SetupTokenFilename is the file under the data dir holding the one-time first-admin token.

Variables ¶

This section is empty.

Functions ¶

func AttentionResolvedWindow ¶ added in v0.2.0

func AttentionResolvedWindow() time.Duration

AttentionResolvedWindow is how long expired, denied and approved device logins stay visible, APP_ATTENTION_RESOLVED_WINDOW.

func BootstrapAdmin ¶

func BootstrapAdmin(ctx context.Context, s AuthStore, username, password string) error

BootstrapAdmin ensures at least one user exists, creating one from username/password if none does yet, with AbilityRoot: it's the only user on the instance, so it has to be. No-op once any user exists, so a restart with the same env vars never resets a changed password. username becomes the new user's email verbatim (no "@" required), matching migrations/0035's backfill leniency for a pre-existing admin_user row.

func DeviceCodeTTL ¶ added in v0.2.0

func DeviceCodeTTL() time.Duration

DeviceCodeTTL is how long a device login request stays approvable, APP_DEVICE_CODE_TTL as a Go duration when set.

func DeviceTokenTTL ¶ added in v0.2.0

func DeviceTokenTTL() time.Duration

DeviceTokenTTL is the lifetime of a device-login token (APP_DEVICE_TOKEN_TTL_DAYS).

func DockerGuardAuditEntry ¶ added in v0.2.0

func DockerGuardAuditEntry(d dockerguard.Decision) (store.AuditEntry, error)

DockerGuardAuditEntry turns a guard decision into an audit_log row. The path carries the container name, never the request body.

func EnsureSetupToken ¶ added in v0.2.0

func EnsureSetupToken(ctx context.Context, users UserCounter, dataDir string) (token string, created bool, err error)

EnsureSetupToken returns the setup token for an instance with no users, creating it (mode 0600) if missing; created reports whether it is new. Once any user exists the file is removed and "" is returned.

func HostFirewallSSHPorts ¶ added in v0.2.0

func HostFirewallSSHPorts() []int

HostFirewallSSHPorts exposes the SSH ports the firewall switch keeps open, so the exposure lockout guard protects the same set.

func MaybeBootstrapDevAdmin ¶

func MaybeBootstrapDevAdmin(ctx context.Context, s AuthStore, logger *slog.Logger) error

MaybeBootstrapDevAdmin is cmd/levelrail/main.go's entry point, called unconditionally on every startup the same way BootstrapAdmin already is: a no-op unless APP_DEV_MODE=1 in a non -tags embedweb build (see devmode_debug.go/devmode_release.go). BootstrapAdmin itself is a no-op once any admin account exists, so this only ever creates the fixed dev/dev account on a genuinely empty data directory; an operator who already set APP_ADMIN_USERNAME/APP_ADMIN_PASSWORD keeps that account regardless of call order between the two.

func MaybeSeedDevFixturesFromFile ¶

func MaybeSeedDevFixturesFromFile(ctx context.Context, s TokenStore, logger *slog.Logger, path string) error

MaybeSeedDevFixturesFromFile is cmd/levelrail/main.go's entry point, called unconditionally on every startup the same way MaybeBootstrapDevAdmin already is. A missing fixtures file is not an error: dev mode works without one (only the fixed dev/dev admin account), a dev-fixtures.yml is an opt-in convenience layered on top.

func MintAIAssistantToken ¶ added in v0.2.0

func MintAIAssistantToken(ctx context.Context, tokens TokenStore) (string, error)

MintAIAssistantToken mints the root token the in-app assistant calls the API with, labeled so the AI control gate and the audit log treat it as an agent.

func MintAPIToken ¶ added in v0.2.0

func MintAPIToken(ctx context.Context, tokens TokenStore, name string, abilities []string, expiresAt *time.Time) (string, store.APIToken, error)

MintAPIToken generates, hashes, and persists a new API token, returning its plaintext exactly once, same as handleCreateToken's own response. Exported so a non-HTTP caller (cmd/levelrail's own internal AI assistant self-call token, minted once at startup rather than by a human through this handler) can mint through the identical path instead of duplicating it.

func MintAgentAPIToken ¶ added in v0.2.0

func MintAgentAPIToken(ctx context.Context, tokens TokenStore, name string, abilities []string, expiresAt *time.Time, agent agentIdentity, ownerUserID string) (string, store.APIToken, error)

MintAgentAPIToken is MintAPIToken with an agent label and an owning user (empty for system tokens).

func NewDeviceApprovalFromEnv ¶ added in v0.2.0

func NewDeviceApprovalFromEnv() bool

NewDeviceApprovalFromEnv reads APP_AUTH_NEW_DEVICE_APPROVAL: on unless set to a false value, the break-glass switch for a locked-out operator.

func ReadSetupToken ¶ added in v0.2.0

func ReadSetupToken(dataDir string) (string, error)

ReadSetupToken returns the current setup token, or "" if none exists.

func SetupTokenPath ¶ added in v0.2.0

func SetupTokenPath(dataDir string) string

SetupTokenPath returns the setup token file path inside dataDir.

Types ¶

type AIAssistantSecrets ¶ added in v0.2.0

type AIAssistantSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

AIAssistantSecrets is the internal/secrets.Manager surface PUT/DELETE need to store and clear the BYOK key. nil is valid: both routes return 501 without one configured, the same shape EmailSecretsStore's absence produces; GET works regardless.

type AIAssistantSettingsStore ¶ added in v0.2.0

type AIAssistantSettingsStore interface {
	GetAIAssistantSettings(ctx context.Context) (store.AIAssistantSettings, error)
	UpdateAIAssistantSettings(ctx context.Context, s store.AIAssistantSettings) error
}

AIAssistantSettingsStore is the core internal/store surface GET/PUT/ DELETE /api/v1/settings/ai-assistant need: always set, the provider/ model row always exists (migrations/0106's own seeded row), the same "core Store interface" shape ingressSettings/emailSettings already establish.

type AIChatStore ¶ added in v0.2.0

type AIChatStore interface {
	CreateAIChatSession(ctx context.Context, id string, now time.Time) (store.AIChatSession, error)
	GetAIChatSession(ctx context.Context, id string) (store.AIChatSession, error)
	ListAIChatSessions(ctx context.Context) ([]store.AIChatSession, error)
	DeleteAIChatSession(ctx context.Context, id string) error
	ListAIChatMessages(ctx context.Context, sessionID string) ([]store.AIChatMessage, error)
	GetAIChatConfirmation(ctx context.Context, id string) (store.AIChatConfirmation, error)
}

AIChatStore is the core internal/store surface the chat session routes need: always set, the same "core Store interface" shape certs/ staticSites already establish, since a session/message/confirmation row's absence is always a legitimate, queryable "not found", not a "not configured" condition.

type AIControlStore ¶ added in v0.2.0

type AIControlStore interface {
	GetAIControlSettings(ctx context.Context) (store.AIControlSettings, error)
	UpdateAIControlSettings(ctx context.Context, mode string, kinds []string, updatedBy string) error
	RevokeAgentTokens(ctx context.Context, internalName, internalAgent string) (int, error)
	CountAgentTokens(ctx context.Context, internalName, internalAgent string) (int, error)
	EnvironmentOfApp(ctx context.Context, appName string) (*store.EnvironmentRef, error)
	EnvironmentOfDatabase(ctx context.Context, databaseName string) (*store.EnvironmentRef, error)
}

AIControlStore is the store surface the AI control gate and settings routes need.

type AIEngine ¶ added in v0.2.0

type AIEngine interface {
	IsConfigured(ctx context.Context) (bool, error)
	RunTurn(ctx context.Context, sessionID, userContent string, sink ai.Sink) error
	ResolveConfirmation(ctx context.Context, sessionID, confirmationID string, approve bool, sink ai.Sink) error
}

AIEngine is the internal/ai.Engine surface the chat routes need. nil is valid: every /api/v1/ai/... route returns 501 without one configured, the same "not configured" shape secrets/telemetry's absence already produce elsewhere in this package.

type AlertNoise ¶ added in v0.2.0

type AlertNoise interface {
	CreateSilence(ctx context.Context, s alerting.Silence) error
	GetSilence(ctx context.Context, id string) (*alerting.Silence, error)
	ListSilences(ctx context.Context, now time.Time, includeExpired bool, limit int) ([]alerting.Silence, error)
	ListActiveSilences(ctx context.Context, now time.Time) ([]alerting.Silence, error)
	ExpireSilence(ctx context.Context, id string, now time.Time) (*alerting.Silence, error)
	SaveMaintenanceWindow(ctx context.Context, w alerting.MaintenanceWindow) error
	GetMaintenanceWindow(ctx context.Context, id string) (*alerting.MaintenanceWindow, error)
	ListMaintenanceWindows(ctx context.Context) ([]alerting.MaintenanceWindow, error)
	DeleteMaintenanceWindow(ctx context.Context, id string) error
	ListHistory(ctx context.Context, f alerting.HistoryFilter) ([]alerting.HistoryEntry, error)
}

AlertNoise is the surface the silence, maintenance window and alert history handlers need from internal/alerting.DB.

type AlertRules ¶

type AlertRules interface {
	SaveRule(ctx context.Context, r alerting.Rule) error
	GetRule(ctx context.Context, id string) (*alerting.Rule, error)
	ListRulesForResource(ctx context.Context, resourceID string) ([]alerting.Rule, error)
	DeleteRule(ctx context.Context, id string) error
}

AlertRules is the surface the alert-rule handlers need from internal/alerting.DB. *alerting.DB satisfies this structurally, the same "narrow consumer-defined interface" convention TelemetryQuerier and SecretSetter already establish in this package.

type AppComposeStore ¶

type AppComposeStore interface {
	SaveApp(ctx context.Context, a store.App) error
	GetAppByName(ctx context.Context, name string) (store.App, error)
	SaveDesiredService(ctx context.Context, svc store.DesiredService) error
}

AppComposeStore is the store surface POST /api/v1/apps/{name}/compose needs (apps_compose.go): create-or-reuse the owning store.App, then save each of its member services.

type AppGroupLister ¶

type AppGroupLister interface {
	ListServicesByApp(ctx context.Context, appID string) ([]store.DesiredService, error)
	DeleteApp(ctx context.Context, id string) error
	SaveApp(ctx context.Context, a store.App) error
	GetAppByName(ctx context.Context, name string) (store.App, error)
}

AppGroupLister is the store surface GET /api/v1/apps/{name}/group (apps_group.go) and the app-linking write path (apps_multi.go's ensureAppLinked, apps.go's deleteAppIfOrphaned) need: stage 1 of multi-service apps (migrations/0039_apps.sql) started this as a read-only interface; stage 2 (per-app Docker networking) added the store.App CRUD every create/delete path needs to keep that App row's lifecycle in sync with its member services, since internal/reconcile/application.NetworkCleanupController diffs exactly the App rows this interface reports against Docker's own observed networks. *store.DB satisfies this structurally.

type AppImportStore ¶ added in v0.2.0

type AppImportStore interface {
	CreateAppImportSession(ctx context.Context, s store.AppImportSession, items []store.AppImportItem) error
	GetAppImportSession(ctx context.Context, id string) (store.AppImportSession, error)
	ListAppImportSessions(ctx context.Context) ([]store.AppImportSession, error)
	UpdateAppImportSession(ctx context.Context, s store.AppImportSession, now time.Time) error
	DeleteAppImportSession(ctx context.Context, id string) error
	ListAppImportItems(ctx context.Context, sessionID string) ([]store.AppImportItem, error)
	SaveAppImportItem(ctx context.Context, it store.AppImportItem, now time.Time) error
}

AppImportStore is the store surface the guided app import routes need.

type AppIntegrationStore ¶ added in v0.2.0

type AppIntegrationStore interface {
	SaveAppIntegration(ctx context.Context, ai store.AppIntegration) error
	GetAppIntegration(ctx context.Context, id string) (store.AppIntegration, error)
	ListAppIntegrationsForService(ctx context.Context, serviceName string) ([]store.AppIntegration, error)
	DeleteAppIntegration(ctx context.Context, id string) error
}

AppIntegrationStore is the store surface the app integration handlers need, mirroring ScheduledTaskStore's own shape for a different per-app child resource.

type AppScheduleStore ¶ added in v0.2.0

type AppScheduleStore interface {
	GetAppSchedule(ctx context.Context, serviceName string) (*store.AppSchedule, error)
	SetAppSchedule(ctx context.Context, serviceName, cron, branch, timezone string, enabled bool) error
	ArmAppScheduleNextRun(ctx context.Context, serviceName string, next time.Time) error
	ListAppScheduleHistory(ctx context.Context, serviceName string, limit int) ([]store.AppScheduleHistoryEntry, error)
}

AppScheduleStore is the store surface the schedule handlers and internal/scheduledeploy.Scheduler both need. *store.DB satisfies this structurally.

type AppSleepStore ¶ added in v0.2.0

type AppSleepStore interface {
	SetAppSleepIdle(ctx context.Context, serviceName string, minutes int, now time.Time) error
	SetAppSleeping(ctx context.Context, serviceName string, sleeping bool, now time.Time) error
	SetAppSleepHold(ctx context.Context, serviceName string, hold bool, now time.Time) error
	GetAppSleep(ctx context.Context, serviceName string) (store.AppSleep, error)
	ListAppSleep(ctx context.Context) ([]store.AppSleep, error)
}

AppSleepStore is the store surface the sleep routes need.

type AppStore ¶

type AppStore interface {
	SaveDesiredService(ctx context.Context, svc store.DesiredService) error
	GetDesiredService(ctx context.Context, name string) (*store.DesiredService, error)
	ListDesiredServices(ctx context.Context) ([]store.DesiredService, error)
	DeleteDesiredService(ctx context.Context, name string) error
	// ListAppStreamsForService backs internal/reconcile/application's
	// ServiceStore (the same interface this type satisfies for the
	// application controller's own New call): one extra published
	// container port per raw TCP stream (migrations/0279_app_streams.sql).
	ListAppStreamsForService(ctx context.Context, serviceName string) ([]store.AppStream, error)
	// UpdateServiceNode is the placement mutation, separate
	// from SaveDesiredService on purpose: see store.DB.SaveDesiredService's
	// own doc comment for why an ordinary app update must never be able
	// to silently move a service between nodes.
	UpdateServiceNode(ctx context.Context, name, nodeID string) error
	// ListDesiredServicesByNode is the drain and
	// delete-guard primitive (handleDrainNode, handleDeleteNode): find
	// what's placed on a node without listing every service.
	ListDesiredServicesByNode(ctx context.Context, nodeID string) ([]store.DesiredService, error)
	// ListDesiredServicesByProject is ListDesiredServicesByNode's
	// project-kind counterpart: handleStopProject/handleStartProject
	// (project_stop_start.go) and handleRestartProject
	// (project_restart.go) use it to find every app in a project without
	// listing every service.
	ListDesiredServicesByProject(ctx context.Context, projectID string) ([]store.DesiredService, error)
	// ListDesiredServicesByEnvironment is ListDesiredServicesByProject's
	// environment-kind counterpart: environment_clone.go's own clone
	// preview/execute handlers use it to find every app tagged with the
	// environment being cloned.
	ListDesiredServicesByEnvironment(ctx context.Context, environmentID string) ([]store.DesiredService, error)
	// RestartService is the only way to force a running container to be
	// recreated without an image change: a redeploy of the same image
	// tag is otherwise a genuine reconciler no-op (see
	// internal/reconcile/application.ContainerName's own doc comment).
	// See store.DB.RestartService's own doc comment for why this is
	// deliberately excluded from SaveDesiredService's full-record-replace
	// semantics, the same reasoning UpdateServiceNode already establishes
	// for NodeID.
	RestartService(ctx context.Context, name string) error
	// UpdateServiceProject is UpdateServiceNode's project-kind
	// counterpart (projects.go): same separation-from-ordinary-update
	// reasoning, see store.DB.UpdateServiceProject's own doc comment.
	UpdateServiceProject(ctx context.Context, name, projectID string) error
	// UpdateServiceStorageTarget backs PUT/DELETE
	// /api/v1/apps/{name}/storage (apps_storage.go): which store.BackupTarget
	// (already-established interface, BackupTargetStore below) this app's
	// own object-storage credentials resolve from. Same
	// separation-from-ordinary-update reasoning as UpdateServiceNode/
	// UpdateServiceProject, see store.DB.UpdateServiceStorageTarget's own
	// doc comment.
	UpdateServiceStorageTarget(ctx context.Context, name, storageTargetID string) error
	// UpdateServiceSuspended backs POST /api/v1/apps/{name}/stop and
	// .../start (handleStopApp/handleStartApp): same
	// separation-from-ordinary-update reasoning as UpdateServiceNode/
	// UpdateServiceProject/UpdateServiceStorageTarget, see
	// store.DB.UpdateServiceSuspended's own doc comment.
	UpdateServiceSuspended(ctx context.Context, name string, suspended bool) error
	// UpdateServiceApp is stage 2 of multi-service apps
	// (migrations/0039_apps.sql)'s own service-side setter: which
	// store.App a service belongs to. Same separation-from-ordinary-
	// update reasoning as UpdateServiceNode/UpdateServiceProject/
	// UpdateServiceStorageTarget/UpdateServiceSuspended, see
	// store.DB.UpdateServiceApp's own doc comment. Called from
	// ensureAppLinked (apps_multi.go), the shared create-or-reuse-and-link
	// path both handleCreateApp (an ordinary single-service app) and
	// handleDeploySpec (a multi-service fan-out) go through.
	UpdateServiceApp(ctx context.Context, name, appID string) error
	// UpdateServiceLogDrain backs PUT/DELETE
	// /api/v1/apps/{name}/log-drain (apps_log_drain.go): which external
	// sink (internal/telemetry.DrainForwarder) this app's container logs
	// additionally forward to. Same separation-from-ordinary-update
	// reasoning as UpdateServiceStorageTarget, see
	// store.DB.UpdateServiceLogDrain's own doc comment.
	UpdateServiceLogDrain(ctx context.Context, name string, drain *store.LogDrain) error
	// UpdateServiceDatabaseAttachment backs PUT/DELETE
	// /api/v1/apps/{name}/database (apps_database.go): which managed
	// database (DatabaseStore below) this app resolves one connection env
	// var from. Same separation-from-ordinary-update reasoning as
	// UpdateServiceStorageTarget, see store.DB.UpdateServiceDatabaseAttachment's
	// own doc comment.
	UpdateServiceDatabaseAttachment(ctx context.Context, name string, att *store.DatabaseAttachment) error
	// SetServiceAutoRollbackOnCrashloop backs PUT
	// /api/v1/apps/{name}/auto-rollback (deploys.go): whether
	// internal/alerting.MaybeAutoRollback rolls this app back
	// automatically the next time a KindCrashloop rule fires for it. Same
	// separation-from-ordinary-update reasoning as UpdateServiceStorageTarget,
	// see store.DB.SetServiceAutoRollbackOnCrashloop's own doc comment.
	SetServiceAutoRollbackOnCrashloop(ctx context.Context, name string, enabled bool) error
	// SetServiceAutoRollbackOnSLOBurn backs PUT
	// /api/v1/apps/{name}/auto-rollback-slo-burn (deploys.go): which mode
	// internal/alerting.MaybeAutoRollbackOnSLOBurn acts in the next time a
	// KindSLOBurn rule fires for this app. Same separation-from-ordinary-
	// update reasoning as SetServiceAutoRollbackOnCrashloop, see
	// store.DB.SetServiceAutoRollbackOnSLOBurn's own doc comment.
	SetServiceAutoRollbackOnSLOBurn(ctx context.Context, name, mode string) error
	// SetServiceExecEnabled backs PUT /api/v1/apps/{name}/exec-access
	// (exec.go): whether POST .../exec and GET .../terminal are even
	// attempted for this app, independent of the caller's own IAM
	// abilities. Same separation-from-ordinary-update reasoning as
	// SetServiceAutoRollbackOnCrashloop, see
	// store.DB.SetServiceExecEnabled's own doc comment.
	SetServiceExecEnabled(ctx context.Context, name string, enabled bool) error
	// SetServiceBadgeEnabled backs PUT /api/v1/apps/{name}/badge
	// (app_badge.go): whether GET .../badge.svg serves anything for
	// this app, or 404s. Same separation-from-ordinary-update reasoning
	// as SetServiceAutoRollbackOnCrashloop, see
	// store.DB.SetServiceBadgeEnabled's own doc comment.
	SetServiceBadgeEnabled(ctx context.Context, name string, enabled bool) error
	// SetServiceVaultEnvVar backs PUT/DELETE
	// /api/v1/apps/{name}/vault-env/{key} (apps_vault_env.go): the
	// UI/CLI-facing way to declare (or remove) one Vault-sourced env var
	// on an app that already exists, without going through the general
	// PUT /api/v1/apps/{name}, which would silently drop every other
	// declarative field (VaultEnv included) it doesn't itself carry
	// forward. Same separation-from-ordinary-update reasoning as
	// UpdateServiceDatabaseAttachment, see store.DB.SetServiceVaultEnvVar's
	// own doc comment.
	SetServiceVaultEnvVar(ctx context.Context, name, envVar string, ref *store.VaultEnvRef) error
	// SetServiceDatabaseEnvVar backs POST/DELETE
	// /api/v1/apps/{name}/connections[/{env_var}] (apps_connections.go):
	// the UI/CLI-facing way to declare (or remove) one entry in an app's
	// DatabaseEnv map, letting an app connect to more than one managed
	// database without app.yaml. Same separation-from-ordinary-update
	// reasoning as SetServiceVaultEnvVar, see
	// store.DB.SetServiceDatabaseEnvVar's own doc comment.
	SetServiceDatabaseEnvVar(ctx context.Context, name, envVar string, ref *store.DatabaseEnvRef) error
	// SetServicePreviewEnvOverride backs PUT/DELETE
	// /api/v1/apps/{name}/preview-env/{key} (apps_preview_env.go): the
	// UI/CLI-facing way to declare (or remove), on an app that already
	// exists, a preview-specific value for one env var that replaces
	// whatever this app's own Env/SecretEnv/VaultEnv holds for that key,
	// applied only when a preview environment is next created from this
	// app (deployPreviewSingle, preview_environments.go), never to this
	// app's own deploy. Same separation-from-ordinary-update reasoning as
	// SetServiceVaultEnvVar, see store.DB.SetServicePreviewEnvOverride's
	// own doc comment.
	SetServicePreviewEnvOverride(ctx context.Context, name, envVar string, value *string) error
	// UpdateServiceEgressPolicy backs GET/PUT/DELETE
	// /api/v1/apps/{name}/egress-policy (apps_egress.go): the UI/CLI-
	// facing way to set (or clear) an app's outbound network allowlist
	// outside a redeploy, mirroring SetServiceVaultEnvVar's own dual
	// write path (app.yaml's egress: block also sets this, via the
	// ordinary SaveDesiredService full-record-replace). Same
	// separation-from-ordinary-update reasoning as
	// UpdateServiceStorageTarget, see
	// store.DB.UpdateServiceEgressPolicy's own doc comment.
	UpdateServiceEgressPolicy(ctx context.Context, name string, policy *store.ServiceEgressPolicy) error
	// UpdateServiceHealth backs PUT /api/v1/apps/{name}/health (apps_health.go).
	UpdateServiceHealth(ctx context.Context, name string, health *store.ServiceHealth) error
	// UpdateServiceVolumes backs PUT /api/v1/apps/{name}/volumes
	// (apps_volumes_attach.go): the UI/CLI-facing way to attach or
	// remove a named Docker volume outside a redeploy, mirroring
	// UpdateServiceEgressPolicy's own dual write path. See
	// store.DB.UpdateServiceVolumes's own doc comment.
	UpdateServiceVolumes(ctx context.Context, name string, volumes []store.ServiceVolume) error
	// SetServiceBranchEnvOverride, DeleteServiceBranchEnvOverride, and
	// ListServiceBranchEnvOverrides back POST/DELETE/GET
	// /api/v1/apps/{name}/branch-env (apps_branch_env.go): a narrower,
	// branch-pattern-matched sibling of SetServicePreviewEnvOverride
	// above, applied only when a preview's own branch matches, see
	// store.SetServiceBranchEnvOverride's own doc comment.
	SetServiceBranchEnvOverride(ctx context.Context, serviceName, branchPattern, key, value string, secret bool) (string, error)
	DeleteServiceBranchEnvOverride(ctx context.Context, serviceName, id string) error
	ListServiceBranchEnvOverrides(ctx context.Context, serviceName string) ([]store.ServiceBranchEnvOverride, error)
}

AppStore is the store surface the apps and deploys handlers need. *store.DB satisfies this structurally; tests use a real temp-file store the same way internal/store's own tests do, not a mock.

type AppStreamStore ¶ added in v0.2.0

type AppStreamStore interface {
	SaveAppStream(ctx context.Context, s store.AppStream) error
	ListAppStreamsForService(ctx context.Context, serviceName string) ([]store.AppStream, error)
	ListAllAppStreams(ctx context.Context) ([]store.AppStream, error)
	DeleteAppStream(ctx context.Context, id string) error
	GetAppStream(ctx context.Context, id string) (store.AppStream, error)
}

AppStreamStore is the store surface the app stream handlers need.

type AppVolumeMoveStore ¶ added in v0.2.0

type AppVolumeMoveStore interface {
	StartAppVolumeMove(ctx context.Context, m store.AppVolumeMove) error
	UpdateAppVolumeMoveSteps(ctx context.Context, id string, steps []store.AppVolumeMoveStep) error
	FinishAppVolumeMove(ctx context.Context, id, status, errMsg, finishedAt string) error
	GetAppVolumeMove(ctx context.Context, id string) (store.AppVolumeMove, error)
	ListAppVolumeMoves(ctx context.Context, serviceName string) ([]store.AppVolumeMove, error)
}

AppVolumeMoveStore is the store surface handleMoveAppWithVolumes and its history endpoints need: recording a "move with volumes" attempt's steps and final outcome as it happens, so a caller can see exactly how far a partially-failed move got instead of a single black-box result. *store.DB satisfies this structurally.

type ApprovalChatNotifier ¶ added in v0.2.0

type ApprovalChatNotifier interface {
	SendApprovalRequest(ctx context.Context, c alerting.NotificationChannel, info alerting.ApprovalRequestInfo) error
}

ApprovalChatNotifier is the surface requestDeployApproval needs to post an interactive Approve/Deny message. *alerting.DeployDispatcher satisfies this structurally, the same "one dispatcher, several narrow interfaces" shape NotificationChannelTester already establishes.

type AuditStore ¶

type AuditStore interface {
	SaveAuditEntry(ctx context.Context, e store.AuditEntry) error
	ListAuditEntries(ctx context.Context, limit int, before *time.Time, filter store.AuditEntryFilter) ([]store.AuditEntry, error)
	DeleteAuditEntriesOlderThan(ctx context.Context, cutoff time.Time) (int64, error)
}

AuditStore is the store surface requireAbility's audit hook (internal/api/auth.go), GET /api/v1/audit-log (audit.go), and the retention sweep/manual purge (audit_retention.go) need: insert-only recording, newest-first cursor-paginated reading, and age-based deletion. Always set, the same "core Store interface, not an optional plug-in" shape certs/staticSites/backupTargets already have.

type AuthLibOAuth ¶ added in v0.2.0

type AuthLibOAuth interface {
	OAuthEnabled() bool
	Prefix() string
	OAuthStart(r *http.Request, provider string) authengine.OAuthOutcome
	OAuthCallback(r *http.Request, provider string) authengine.OAuthOutcome
	ResolveOAuthUser(ctx context.Context, provider, providerUserID string) (string, error)
	LinkOAuthIdentity(ctx context.Context, legacyUserID, provider, providerUserID string) error
	InvalidateProvider(name string)
}

AuthLibOAuth is the slice of the library auth engine the OAuth sign-in routes drive.

type AuthStore ¶

type AuthStore interface {
	GetUserByEmail(ctx context.Context, email string) (*store.User, error)
	GetUserByID(ctx context.Context, id string) (*store.User, error)
	CreateUser(ctx context.Context, u store.User) error
	UpdateUserPasswordHash(ctx context.Context, id string, hash *string) error
	UpdateUserLastLogin(ctx context.Context, id string, when time.Time) error
	CountUsers(ctx context.Context) (int, error)
	ListUsers(ctx context.Context) ([]store.User, error)
	DeleteUser(ctx context.Context, id string) error
	UpdateUserAbilities(ctx context.Context, id string, abilities []string) error
}

AuthStore is the store surface the auth handlers need.

type BackupDeleter ¶ added in v0.2.0

type BackupDeleter interface {
	DeleteBackup(ctx context.Context, historyID string) error
}

BackupDeleter is the surface the backup delete handlers need from internal/backup.DeleteRunner: delete one already-recorded backup attempt, both its stored object (best-effort) and its history row. *backup.DeleteRunner satisfies this structurally; internal/api never imports internal/backup directly, the same boundary BackupRunner's own doc comment describes.

type BackupDownloader ¶

type BackupDownloader interface {
	Download(ctx context.Context, historyID string) (io.ReadCloser, error)
}

BackupDownloader is the surface the backup download handler needs from internal/backup.DownloadRunner: resolve one already-succeeded backup attempt's live object into a stream, the read-only counterpart of BackupRunner and RestoreRunner. *backup.DownloadRunner satisfies this structurally; internal/api never imports internal/backup directly, the same boundary BackupRunner's own doc comment describes.

type BackupHistoryStore ¶

type BackupHistoryStore interface {
	ListBackupHistory(ctx context.Context, databaseName string, limit int, before *time.Time) ([]store.BackupHistory, error)
	ListAllBackupHistory(ctx context.Context, limit int, before *time.Time) ([]store.BackupHistory, error)
	GetBackupHistory(ctx context.Context, id string) (store.BackupHistory, error)
}

BackupHistoryStore is the store surface the backup history handler needs. GetBackupHistory was added for handleTriggerRestore (restore.go): resolving the one backup attempt a restore names, not just listing every attempt for a database, the same "look up one row by ID" need GetBackupTarget already serves for backup targets.

type BackupProtection ¶ added in v0.2.0

type BackupProtection interface {
	Health(ctx context.Context) ([]backup.ResourceHealth, error)
	Encryption() backup.EncryptionStatus
	StartDrill(ctx context.Context, backupID string) (string, error)
	RefreshProtection(ctx context.Context, targetIDs []string) ([]store.BackupTargetProtection, error)
	VolumeExists(ctx context.Context, rtm docker.Runtime, name string) (exists, known bool, err error)
	RestoreVolumeTo(ctx context.Context, rtm docker.Runtime, historyID, sourceService, sourceVolume, newVolume, backupID string) error
}

BackupProtection is the surface the backup health, drill and restore-to routes need from internal/backup.Protection.

type BackupProtectionStore ¶ added in v0.2.0

type BackupProtectionStore interface {
	ListBackupDrills(ctx context.Context, serviceName, volumeName, databaseName string, limit int) ([]store.BackupDrill, error)
	GetBackupDrill(ctx context.Context, id string) (store.BackupDrill, error)
	GetVolumeBackupPolicy(ctx context.Context, serviceName, volumeName string) (store.VolumeBackupPolicy, error)
	SetVolumeBackupPolicy(ctx context.Context, p store.VolumeBackupPolicy) error
	ListBackupTargetProtection(ctx context.Context) ([]store.BackupTargetProtection, error)
}

BackupProtectionStore is the store surface the same routes read and write.

type BackupRunner ¶

type BackupRunner interface {
	RunBackup(ctx context.Context, historyID, databaseName, engine, containerName, targetID string) error
}

BackupRunner is the surface the backup trigger handler needs from internal/backup.Runner: run one backup attempt end to end, from an already-minted history ID through to a finished store.BackupHistory row. *backup.Runner satisfies this structurally; internal/api never imports internal/backup directly (the same reconciler-internals boundary databaseContainerName's own doc comment describes), only this narrow interface, wired in by cmd/levelrail at startup.

type BackupSecretsSetter ¶

type BackupSecretsSetter interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
}

BackupSecretsSetter is the surface backup target creation needs from internal/secrets.Manager: store a credential value, the same "set a value, never read one back through this interface" shape SecretSetter already establishes for app secrets. A distinct type from SecretSetter even though both are structurally satisfied by *secrets.Manager, because a backup target's credentials do get resolved back out server-side, just not through this interface or this package: that's internal/backup.Runner's own, separate, narrower internal/backup.SecretsResolver, wired directly in cmd/levelrail, not through the Router at all.

type BackupTargetStore ¶

type BackupTargetStore interface {
	SaveBackupTarget(ctx context.Context, t store.BackupTarget) error
	GetBackupTarget(ctx context.Context, id string) (store.BackupTarget, error)
	ListBackupTargets(ctx context.Context) ([]store.BackupTarget, error)
	UpdateBackupTarget(ctx context.Context, id, name, provider, endpoint, region, bucket string) error
	DeleteBackupTarget(ctx context.Context, id string) error
}

BackupTargetStore is the store surface the backup target handlers need.

type BackupTargetTester ¶

type BackupTargetTester interface {
	TestTarget(ctx context.Context, targetID string) error
}

BackupTargetTester is the surface the test-connection handler needs from internal/backup.TargetTester: probe one target's configured bucket over its stored credentials, without uploading or deleting anything. *backup.TargetTester satisfies this structurally; internal/api never resolves a backup target's credentials directly, the same boundary BackupSecretsSetter's own doc comment describes.

type BackupVerificationStore ¶

type BackupVerificationStore interface {
	ListBackupVerifications(ctx context.Context, backupHistoryID string, limit int) ([]store.BackupVerification, error)
}

BackupVerificationStore is the store surface the backup verification handlers need: listing past verification attempts for one backup, the same "core Store interface, no runner configuration needed" shape BackupHistoryStore's own doc comment establishes for backup history itself.

type BackupVerifier ¶

type BackupVerifier interface {
	VerifyBackup(ctx context.Context, verificationID, backupHistoryID, engine, checkedBy string) error
}

BackupVerifier is the surface the verify-trigger handler needs from internal/backup.VerifyRunner: run one verification attempt end to end, from an already-minted verification ID through to a finished store.BackupVerification row. *backup.VerifyRunner satisfies this structurally; internal/api never imports internal/backup directly, the same boundary BackupRunner's own doc comment describes.

type BaseBackupHistoryStore ¶ added in v0.2.0

type BaseBackupHistoryStore interface {
	ListBaseBackupHistory(ctx context.Context, databaseName string) ([]store.BaseBackupHistory, error)
}

BaseBackupHistoryStore is the store surface the base backup handlers need.

type BaseBackupRunner ¶ added in v0.2.0

type BaseBackupRunner interface {
	RunBaseBackup(ctx context.Context, historyID, databaseName, containerName, targetID string) error
}

BaseBackupRunner is the surface the base backup trigger handler needs from internal/backup.BaseBackupRunner: run one physical base backup attempt end to end. *backup.BaseBackupRunner satisfies this structurally; internal/api never imports internal/backup directly (databaseContainerName's own doc comment describes this boundary), only this narrow interface, wired in by cmd/levelrail at startup.

type BitbucketAppClient ¶

type BitbucketAppClient interface {
	ExchangeCode(ctx context.Context, key, secret, code string) (bitbucketapp.Tokens, error)
	RefreshToken(ctx context.Context, key, secret, refreshToken string) (bitbucketapp.Tokens, error)
	ListRepos(ctx context.Context, accessToken string) ([]bitbucketapp.Repo, error)
	GetRepo(ctx context.Context, accessToken, fullName string) (bitbucketapp.Repo, error)
	ListBranches(ctx context.Context, accessToken, fullName string) ([]bitbucketapp.Branch, error)
	CreateRepoWebhook(ctx context.Context, accessToken, fullName, hookURL, secret string) error
	CreatePullRequestComment(ctx context.Context, accessToken, fullName string, prID int, body string) (int64, error)
	ListPullRequestComments(ctx context.Context, accessToken, fullName string, prID int) ([]gitprovider.Comment, error)
	UpdatePullRequestComment(ctx context.Context, accessToken, fullName string, prID int, commentID int64, body string) error
	CreateCommitBuildStatus(ctx context.Context, accessToken, fullName, commit string, state bitbucketapp.BuildStatusState, targetURL, description, key string) error
}

BitbucketAppClient is the surface internal/api needs from internal/bitbucketapp.Client: OAuth code exchange/refresh, and repo listing/lookup/webhook registration once connected. *bitbucketapp.Client satisfies this structurally; tests substitute a hand-written fake.

type BitbucketAppSecrets ¶

type BitbucketAppSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

BitbucketAppSecrets is the surface the Bitbucket App handlers need from internal/secrets.Manager, the same "writes and reads back through internal/secrets" shape GitLabAppSecrets's own doc comment describes: the consumer's own secret is set once at connect time, access_token/refresh_token/token_expires_at are written and re-read on the same schedule by bitbucketAccessToken.

type BitbucketAppStore ¶

type BitbucketAppStore interface {
	GetBitbucketAppConnection(ctx context.Context) (store.BitbucketAppConnection, error)
	SaveBitbucketAppConnection(ctx context.Context, c store.BitbucketAppConnection) error
	DeleteBitbucketAppConnection(ctx context.Context) error
}

BitbucketAppStore is the store surface the Bitbucket App connection handlers need. *store.DB satisfies this structurally, the same "core store" shape GitHubAppStore/GitLabAppStore's own doc comments establish.

type BuildCacheService ¶ added in v0.2.0

type BuildCacheService interface {
	KeyPrefix(app string) (string, error)
	Stats(ctx context.Context, app string) (objectstore.CacheStats, error)
	Clear(ctx context.Context, app string) (objectstore.ClearResult, error)
}

BuildCacheService runs bucket-side cache hygiene.

type BuildCacheSettingsStore ¶ added in v0.2.0

type BuildCacheSettingsStore interface {
	UpsertBuildCacheSetting(ctx context.Context, s store.BuildCacheSetting) error
	GetBuildCacheSetting(ctx context.Context, appName string) (store.BuildCacheSetting, error)
	ListBuildCacheSettings(ctx context.Context) ([]store.BuildCacheSetting, error)
	DeleteBuildCacheSetting(ctx context.Context, appName string) error
}

BuildCacheSettingsStore persists build cache settings.

type Builder ¶

type Builder interface {
	Deploy(ctx context.Context, req deploy.Request, progress func(build.ProgressEvent)) (string, error)
	// DeploySpec is handleDeploySpec's (apps_multi.go) own narrow need:
	// the multi-service fan-out entry point. Same *deploy.Pipeline
	// satisfies both methods, so this stays one interface rather than a
	// second router field, the same "one concrete type, one interface"
	// shape every other narrow store/builder interface in this codebase
	// already follows.
	DeploySpec(ctx context.Context, req deploy.MultiRequest, progress func(serviceKey string, ev build.ProgressEvent)) ([]deploy.ServiceOutcome, error)
}

Builder is the surface the manual build trigger handler (handleTriggerBuild) needs from internal/deploy.Pipeline, the exact same narrow-interface shape internal/webhook.Deployer already establishes for the identical method, so both call sites can be satisfied by the one real *deploy.Pipeline cmd/levelrail/main.go builds. *deploy.Pipeline satisfies this structurally.

type CanaryStore ¶ added in v0.2.0

type CanaryStore interface {
	SetCanaryRelease(ctx context.Context, c store.CanaryRelease) error
	GetCanaryRelease(ctx context.Context, serviceName string) (store.CanaryRelease, bool, error)
	DeleteCanaryRelease(ctx context.Context, serviceName string) error
	ListCanaryReleases(ctx context.Context) ([]store.CanaryRelease, error)
}

CanaryStore is the store surface the canary routes need.

type CertObservationSource ¶ added in v0.2.0

type CertObservationSource interface {
	ListAllCertExpiryObservations(ctx context.Context) ([]alerting.CertExpiryObservation, error)
}

CertObservationSource lists the expiry observations kind=cert_expiry rules record; *alerting.DB satisfies it.

type CertStore ¶

type CertStore interface {
	ListCertStorageKeys(ctx context.Context, prefix string, recursive bool) ([]string, error)
	GetCertStorageValue(ctx context.Context, key string) (*store.CertStorageValue, error)
	DeleteCertStorageValue(ctx context.Context, key string) error
}

CertStore is the store surface handleListCertificates and handleRenewDomainCertificate need: the two read methods internal/ingress.SQLiteStorage also uses, plus the delete method renew needs to clear a domain's stored certificate. *store.DB satisfies this structurally, same as every other Store sub-interface in this package.

type CloneRestoreHistoryStore ¶

type CloneRestoreHistoryStore interface {
	ListCloneRestores(ctx context.Context, sourceDatabaseName string) ([]store.CloneRestore, error)
}

CloneRestoreHistoryStore is the store surface the clone-restore history handler needs, the "restore as new database" counterpart to RestoreHistoryStore (restore.go).

type CloneRestoreRunner ¶

type CloneRestoreRunner interface {
	RunCloneRestore(ctx context.Context, historyID, sourceDatabaseName, newDatabaseName, backupHistoryID, engine, containerName, controllerName string) error
}

CloneRestoreRunner is the surface the clone-restore trigger handler needs from internal/backup.CloneRestoreRunner: wait for a newly created database to come up and restore a backup into it, from an already-minted history ID through to a finished store.CloneRestore row. *backup.CloneRestoreRunner satisfies this structurally; internal/api never imports internal/backup directly, the same boundary RestoreRunner's own doc comment describes.

type CloudflareDNSSecrets ¶

type CloudflareDNSSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

CloudflareDNSSecrets is the surface the Cloudflare DNS-01 settings handlers need from internal/secrets.Manager, the same shape CloudflareTunnelSecrets already establishes for a distinct credential (a scoped Cloudflare API token, not the cloudflared connector token).

type CloudflareDNSStore ¶

type CloudflareDNSStore interface {
	GetCloudflareDNSSettings(ctx context.Context) (store.CloudflareDNSSettings, error)
	UpdateCloudflareDNSSettings(ctx context.Context, s store.CloudflareDNSSettings) error
}

CloudflareDNSStore is the store surface GET/PUT /api/v1/settings/cloudflare-dns need, the same "single platform-wide row" shape CloudflareTunnelStore already establishes.

type CloudflareDNSTokenResolver ¶ added in v0.2.0

type CloudflareDNSTokenResolver interface {
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
}

CloudflareDNSTokenResolver is the narrow surface GET/POST/PUT/DELETE .../dns-records need from internal/secrets.Manager to read back the plaintext Cloudflare DNS-01 token this control plane already stores: CloudflareDNSSecrets (cloudflare_dns.go) only checks presence, it never reads the value back, since the settings resource never needs to. *secrets.Manager satisfies this structurally, the same shape internal/reconcile/ingress's own identically-named interface already establishes for the same credential.

type CloudflareTunnelSecrets ¶

type CloudflareTunnelSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

CloudflareTunnelSecrets is the surface the Cloudflare Tunnel settings handlers need from internal/secrets.Manager: set the token (PUT), check whether one exists (GET's has_token, and PUT's "was a token already set" check for the omitted-token case), and clear it (DELETE). *secrets.Manager satisfies this structurally, the same "narrow, consumer-defined interface" shape GitHubAppSecrets already establishes.

type CloudflareTunnelStore ¶

type CloudflareTunnelStore interface {
	GetCloudflareTunnelSettings(ctx context.Context) (store.CloudflareTunnelSettings, error)
	UpdateCloudflareTunnelSettings(ctx context.Context, s store.CloudflareTunnelSettings) error
}

CloudflareTunnelStore is the store surface GET/PUT/DELETE /api/v1/settings/cloudflare-tunnel need: the single platform-wide row, always present, the same shape EmailSettingsStore has for its own row.

type ComposeRevisionStore ¶ added in v0.2.0

type ComposeRevisionStore interface {
	AddComposeRevision(ctx context.Context, rev store.ComposeRevision) (int, error)
	ListComposeRevisions(ctx context.Context, appID string, limit int) ([]store.ComposeRevision, error)
	GetComposeRevision(ctx context.Context, appID string, revision int) (store.ComposeRevision, error)
	ListComposeApps(ctx context.Context) ([]string, error)
	DeleteComposeRevisions(ctx context.Context, appID string) error
}

ComposeRevisionStore is the store surface compose stacks need for history and rollback.

type ComposeSecretStore ¶

type ComposeSecretStore interface {
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
}

ComposeSecretStore is the surface POST /api/v1/apps/{name}/compose needs to resolve a compose file's SERVICE_ magic vars (compose.ResolveMagicVars): a canonical generate-once-per-app value (Resolve/SetValue keyed by the app's own name, a synthetic bucket distinct from any real service), plus one SetValue per real service that references it, since internal/secrets.Manager itself is keyed per real service, not per app.

type ContainerLister ¶

type ContainerLister interface {
	ListByPrefix(ctx context.Context, prefix string) ([]docker.ContainerState, error)
}

ContainerLister is the surface GET /api/v1/system/containers needs: every container on this node, Levelrail-managed or not, the same "structurally satisfied by *docker.Client, no second divergent surface" reasoning ImageLister above already documents. An empty prefix argument matches every container name (docker.Client's own ListByPrefix implementation), which is exactly this route's point: unlike every other Runtime-backed feature in this codebase, which scopes to one app/database's own containers, this one deliberately wants everything.

type ControlPlaneBackupManager ¶ added in v0.2.0

type ControlPlaneBackupManager interface {
	Create(ctx context.Context) (cpbackup.Info, error)
	List() ([]cpbackup.Info, error)
	Open(name string) (*os.File, cpbackup.Info, error)
	Delete(name string) error
	Verify(ctx context.Context, name string) (cpbackup.VerifyResult, error)
}

ControlPlaneBackupManager is the surface the /system/backups routes need.

type ControlPlaneDR ¶ added in v0.2.0

type ControlPlaneDR interface {
	Status(ctx context.Context) (cpbackup.Status, error)
	UpdateConfig(ctx context.Context, u cpbackup.ConfigUpdate) error
	ListRemote(ctx context.Context) ([]cpbackup.Remote, error)
	StartBackup() error
	StartDrill() error
	BuildEscrow(ctx context.Context, in cpbackup.EscrowInput) (cpbackup.EscrowBundle, error)
	AckEscrow(ctx context.Context) error
}

ControlPlaneDR is the surface the /system/control-plane-dr routes need.

type CustomTemplateStore ¶ added in v0.2.0

type CustomTemplateStore interface {
	SaveCustomTemplate(ctx context.Context, t store.CustomTemplate) error
	GetCustomTemplate(ctx context.Context, id string) (store.CustomTemplate, error)
	ListCustomTemplates(ctx context.Context) ([]store.CustomTemplate, error)
	DeleteCustomTemplate(ctx context.Context, id string) error
}

CustomTemplateStore is the store surface save-as-template needs (service_templates_custom.go): CRUD on operator-defined templates, the growth-loop counterpart to internal/catalog's static built-in catalog. See store.CustomTemplate's own doc comment for why a template row never holds a real secret value.

type DBPinger ¶

type DBPinger interface {
	PingContext(ctx context.Context) error
}

DBPinger is the surface GET /api/v1/system/doctor needs to report the control plane's own SQLite reachability, the same narrow, single-purpose shape DockerPinger above already establishes. *store.DB satisfies this structurally via its embedded *sql.DB's PingContext method.

type DataExportService ¶ added in v0.2.0

type DataExportService interface {
	List(ctx context.Context) ([]store.ExportDestination, error)
	Get(ctx context.Context, id string) (store.ExportDestination, error)
	Create(ctx context.Context, in dataexport.Input) (store.ExportDestination, error)
	Update(ctx context.Context, id string, p dataexport.Patch) (store.ExportDestination, error)
	Delete(ctx context.Context, id string) error
	Test(ctx context.Context, id string) error
	Status(ctx context.Context, id string) (store.ExportStatus, error)
	Series(ctx context.Context, allow dataexport.ResourceFilter) ([]dataexport.Series, error)
	StreamAudit(ctx context.Context, from, to time.Time, fn func(dataexport.AuditRecord) error) error
}

DataExportService is the surface the export routes need from internal/dataexport; *dataexport.Service satisfies it.

type DataImportStore ¶ added in v0.2.0

type DataImportStore interface {
	ClaimDatabaseDataImport(ctx context.Context, name, host string, port int, srcDB string, now time.Time, staleAfter time.Duration) error
	FinishDatabaseDataImport(ctx context.Context, name, status, reason string, checked, mismatched int, detail string, now time.Time) error
	GetDatabaseDataImport(ctx context.Context, name string) (store.DatabaseDataImport, bool, error)
	ListDatabaseDataImports(ctx context.Context) ([]store.DatabaseDataImport, error)
}

DataImportStore is the store surface the live data copy routes need.

type DatabaseAccessStore ¶ added in v0.2.0

type DatabaseAccessStore interface {
	RecordDatabaseAccessUser(ctx context.Context, u store.DatabaseAccessUser) error
	ListDatabaseAccessUsers(ctx context.Context, database, kind string) ([]store.DatabaseAccessUser, error)
	GetDatabaseAccessUser(ctx context.Context, database, role string) (*store.DatabaseAccessUser, error)
	GetDatabaseAccessUserByID(ctx context.Context, id string) (*store.DatabaseAccessUser, error)
	MarkDatabaseAccessUserRevoked(ctx context.Context, database, role string, now time.Time) error
	GetDatabaseAccessSettings(ctx context.Context, database string) (store.DatabaseAccessSettings, error)
	SaveDatabaseAccessSettings(ctx context.Context, s store.DatabaseAccessSettings) error
	ListDatabaseNetworkRuleNotes(ctx context.Context, database string) (map[string]string, error)
	ReplaceDatabaseNetworkRuleNotes(ctx context.Context, database string, notes map[string]string) error
	EnvironmentOfDatabase(ctx context.Context, databaseName string) (*store.EnvironmentRef, error)
	EnvironmentOfApp(ctx context.Context, appName string) (*store.EnvironmentRef, error)
}

DatabaseAccessStore is the persistence the database access, network and temporary credential handlers need. *store.DB satisfies it.

type DatabaseQueryStore ¶ added in v0.2.0

type DatabaseQueryStore interface {
	AddDatabaseQueryHistory(ctx context.Context, h store.DatabaseQueryHistory, keep int) (store.DatabaseQueryHistory, error)
	ListDatabaseQueryHistory(ctx context.Context, database, principalType, principalID string, limit int) ([]store.DatabaseQueryHistory, error)
	ClearDatabaseQueryHistory(ctx context.Context, database, principalType, principalID string) error
	SaveDatabaseQuery(ctx context.Context, q store.DatabaseSavedQuery) (store.DatabaseSavedQuery, error)
	ListDatabaseSavedQueries(ctx context.Context, database, principalType, principalID string) ([]store.DatabaseSavedQuery, error)
	DeleteDatabaseSavedQuery(ctx context.Context, database, principalType, principalID, id string) error
}

DatabaseQueryStore is the store surface the console's history and saved query routes need.

type DatabaseStore ¶

type DatabaseStore interface {
	SaveDesiredDatabase(ctx context.Context, d store.DesiredDatabase) error
	GetDesiredDatabase(ctx context.Context, name string) (*store.DesiredDatabase, error)
	ListDesiredDatabases(ctx context.Context) ([]store.DesiredDatabase, error)
	DeleteDesiredDatabase(ctx context.Context, name string) error
	// UpdateDatabaseNode is AppStore.UpdateServiceNode's counterpart,
	// same separation-from-ordinary-update reasoning. Originally added
	// unexposed by 3.3; handleDrainNode is the first caller with an
	// actual route to reuse it through.
	UpdateDatabaseNode(ctx context.Context, name, nodeID string) error
	// ListDesiredDatabasesByNode is the database-kind counterpart to
	// AppStore.ListDesiredServicesByNode.
	ListDesiredDatabasesByNode(ctx context.Context, nodeID string) ([]store.DesiredDatabase, error)
	// ListDesiredDatabasesByProject is the database-kind counterpart to
	// AppStore.ListDesiredServicesByProject.
	ListDesiredDatabasesByProject(ctx context.Context, projectID string) ([]store.DesiredDatabase, error)
	// UpdateDatabaseSuspended is AppStore.UpdateServiceSuspended's
	// counterpart, backing POST /api/v1/projects/{id}/stop and .../start
	// (project_stop_start.go) and POST /api/v1/databases/{name}/stop and
	// .../start (database_stop_start.go): same separation-from-ordinary-
	// update reasoning as UpdateDatabaseNode/UpdateDatabaseProject, see
	// store.DB.UpdateDatabaseSuspended's own doc comment.
	UpdateDatabaseSuspended(ctx context.Context, name string, suspended bool) error
	// UpdateDatabaseProject is AppStore.UpdateServiceProject's
	// counterpart (projects.go).
	UpdateDatabaseProject(ctx context.Context, name, projectID string) error
	// SetDatabaseBackupSchedule backs
	// PUT/DELETE /api/v1/databases/{name}/backup-schedule (backups.go):
	// wave-2 roadmap item 6, scheduled backups. Same "own endpoint, own
	// store method" separation UpdateDatabaseNode/UpdateDatabaseProject
	// already establish for their own single-purpose updates.
	SetDatabaseBackupSchedule(ctx context.Context, name, targetID, schedule string, retain, retainDays int) error
	// SetDatabasePITR backs POST/DELETE /api/v1/databases/{name}/pitr
	// (pitr.go): same "own endpoint, own store method" separation
	// SetDatabaseBackupSchedule above establishes.
	SetDatabasePITR(ctx context.Context, name string, enabled bool, enabledAt string) error
	// SetDatabasePublicAccess backs
	// PUT/DELETE /api/v1/databases/{name}/public-access
	// (database_public_access.go): the same "own endpoint, own store
	// method" separation SetDatabaseBackupSchedule already establishes,
	// applied to whether this database's container port is bound to a
	// host port. requestedBindAddress empty resolves to
	// store.DefaultBindAddress; ignored when enabled is false. Returns
	// the port actually assigned (0 when disabling).
	SetDatabasePublicAccess(ctx context.Context, name string, enabled bool, requestedPort int, requestedBindAddress string) (int, error)
}

DatabaseStore is the store surface the databases handlers need, the database-kind counterpart to AppStore.

type DatabaseUpgrader ¶ added in v0.2.0

type DatabaseUpgrader interface {
	Overview(ctx context.Context, name string) (dbupgrade.Overview, error)
	EffectivePolicy(ctx context.Context, name string) (dbupgrade.Policy, bool, error)
	SetPolicy(ctx context.Context, name string, p dbupgrade.Policy, inherit bool, by string) error
	UpgradeNow(ctx context.Context, name, version, by string) (store.DBUpgradeRun, error)
	Summary(ctx context.Context, visible func(string) bool) ([]dbupgrade.SummaryItem, error)
}

DatabaseUpgrader is the upgrade advisor, policy store and runner (internal/dbupgrade.Manager).

type DeployApprovalStore ¶ added in v0.2.0

type DeployApprovalStore interface {
	SaveDeployApproval(ctx context.Context, a store.DeployApproval) error
	GetDeployApproval(ctx context.Context, id string) (store.DeployApproval, error)
	ListDeployApprovals(ctx context.Context, status, serviceName string) ([]store.DeployApproval, error)
	DecideDeployApproval(ctx context.Context, id, newStatus, approvedByType, approvedBy, approvedByName, reason, decidedAt string) (bool, error)
}

DeployApprovalStore is the surface deploy_approvals.go needs: create a pending approval gate for a deploy/promote into a protected environment, list/get it, and record a terminal decision (approve, reject, or expire). Always set, the same "core Store interface, not an optional plug-in" shape HookRunStore above establishes.

type DeployAttemptStore ¶

type DeployAttemptStore interface {
	SaveDeployAttempt(ctx context.Context, a store.DeployAttempt) error
	SetDeployAttemptCommit(ctx context.Context, id, image, commitSHA string) error
	FinishDeployAttempt(ctx context.Context, id, status string, finishedAt time.Time, errMsg string) error
	GetDeployAttempt(ctx context.Context, id string) (*store.DeployAttempt, error)
	ListDeployAttempts(ctx context.Context, serviceName string) ([]store.DeployAttempt, error)
	ListFailedDeploysSince(ctx context.Context, since time.Time) ([]store.FailedDeploy, error)
}

DeployAttemptStore is the store surface real deploy-attempt history needs: row-per-attempt CRUD, layered alongside DeployStore above rather than replacing it. See handleDeployHistory's own doc comment for why GET /api/v1/apps/{name}/deploys keeps returning reconcile conditions unchanged (an existing, real frontend consumer, web/src/queries/deploys.ts's useDeployStatus, already depends on that shape) while this interface backs a new, additional endpoint, GET /api/v1/apps/{name}/deploy-attempts, instead of overloading the old one. *store.DB satisfies this structurally.

type DeployGateStore ¶ added in v0.2.0

type DeployGateStore interface {
	ListGateEvents(ctx context.Context, attemptID string) ([]store.DeployGateEvent, error)
	GetDeploySafetySettings(ctx context.Context, serviceName string) (store.DeploySafetySettings, error)
	SaveDeploySafetySettings(ctx context.Context, s store.DeploySafetySettings) error
	ListRollbackPins(ctx context.Context, serviceName string) ([]store.RollbackPin, error)
	GetRollbackPin(ctx context.Context, serviceName, attemptID string) (*store.RollbackPin, error)
}

DeployGateStore is the store surface behind the readiness gate timeline, per-app gate settings and rollback pins. *store.DB satisfies it.

type DeployLogQuerier ¶

type DeployLogQuerier interface {
	QueryDeployLog(ctx context.Context, attemptID string) ([]telemetry.DeployLogEntry, error)
}

DeployLogQuerier is the telemetry-side read surface handleDeployLogStream needs to serve a full replay once an attempt has already finished (see that handler's own doc comment for the in-progress-vs-finished split). *telemetry.DB satisfies this structurally. Optional (nil is valid, the same "not configured" shape WithTelemetryQuerier's own absence already has): without one, a finished attempt's log route returns 501; an in-progress attempt's live tail (backed by deployRecorder below, a separate optional field) is unaffected by this one being unset.

type DeployNotifier ¶

type DeployNotifier interface {
	Dispatch(ctx context.Context, resourceID string, ev alerting.DeployOutcome)
}

DeployNotifier is the narrow surface recordPlainDeployAttempt (deploys.go) and beginBuildDeployAttempt (deploy_attempts.go) need to fire a deploy-outcome notification once a deploy attempt reaches a terminal state. *alerting.DeployDispatcher satisfies this structurally. resourceID is resourceIDForApp(ev.AppName), computed by the caller (see alerting.DeployDispatcher.Dispatch's own doc comment for why it takes resourceID as a parameter rather than importing resourceIDForApp itself).

type DeployNotifyTargets ¶

type DeployNotifyTargets interface {
	SaveDeployTarget(ctx context.Context, t alerting.DeployTarget) error
	GetDeployTarget(ctx context.Context, id string) (*alerting.DeployTarget, error)
	ListDeployTargetsForResource(ctx context.Context, resourceID string) ([]alerting.DeployTarget, error)
	DeleteDeployTarget(ctx context.Context, id string) error
}

DeployNotifyTargets is the surface the deploy-notify-target handlers need from internal/alerting.DB. *alerting.DB satisfies this structurally, the same convention AlertRules already establishes.

type DeployQueueStore ¶ added in v0.2.0

type DeployQueueStore interface {
	ListQueuedDeployAttempts(ctx context.Context) ([]store.DeployAttempt, error)
	ListRunningDeployAttempts(ctx context.Context) ([]store.DeployAttempt, error)
	StartQueuedDeployAttempt(ctx context.Context, id string, now time.Time) (bool, error)
	FinishRunningDeployAttempt(ctx context.Context, id, status string, finishedAt time.Time, errMsg string) (bool, error)
	CancelDeployAttempt(ctx context.Context, id, from, by string, now time.Time) (bool, error)
	SupersedeQueuedDeployAttempt(ctx context.Context, id, by string, now time.Time) (bool, error)
	GetServiceCancelSuperseded(ctx context.Context, name string) (bool, error)
	SetServiceCancelSuperseded(ctx context.Context, name string, enabled bool) error
}

DeployQueueStore is the store surface for queued and canceled deploys. *store.DB satisfies this structurally.

type DeploySafetyStore ¶ added in v0.2.0

type DeploySafetyStore interface {
	deploy.OrderedStore
	deploy.Sequencer
	deploy.FreezeStore
	deploy.HeldStore
	ReplaceDeployFreezeWindows(ctx context.Context, scope string, windows []store.DeployFreezeWindow) ([]store.DeployFreezeWindow, error)
	SetDeployAttemptReason(ctx context.Context, id, reason string) error
	DeployQueueStore
}

DeploySafetyStore is the store surface for deploy ordering, freeze windows and digest history. *store.DB satisfies this structurally.

type DeployStore ¶

type DeployStore interface {
	GetConditions(ctx context.Context, controllerName string) ([]reconcile.Condition, error)
	// GetConditionsForControllers is handleListApps' batched status
	// source (apps.go): one query for every app's application
	// controller, not a GetConditions call per app.
	GetConditionsForControllers(ctx context.Context, controllerNames []string) (map[string][]reconcile.Condition, error)
}

DeployStore is the store surface the deploy-history handler needs.

type DevFixtureToken ¶

type DevFixtureToken struct {
	Name      string   `yaml:"name"`
	Plaintext string   `yaml:"plaintext"`
	Abilities []string `yaml:"abilities"`
}

DevFixtureToken is one entry in a dev-fixtures.yml file: a fixed plaintext API token, minted with a known ability set, so local testing of the auth flow (does a read-only token get 403'd on a write route, does a write:sensitive token reach the secrets handler, and so on) doesn't require registering, logging in, and minting a real token by hand every time. Only ever seeded under dev mode, see devmode_debug.go/devmode_release.go's devModeEnabled gate.

type DevFixtures ¶

type DevFixtures struct {
	Tokens []DevFixtureToken `yaml:"tokens"`
}

DevFixtures is the top-level shape of dev-fixtures.yml.

func ParseDevFixtures ¶

func ParseDevFixtures(data []byte) (*DevFixtures, error)

ParseDevFixtures validates as much as validateAbilities already validates a real token mint request: an empty or unrecognized ability list is rejected here too, so a typo in dev-fixtures.yml fails loudly at startup instead of silently minting a token nobody can use as intended.

type DeviceLoginNotifier ¶ added in v0.2.0

type DeviceLoginNotifier interface {
	SendNotice(ctx context.Context, kind alerting.NotifyKind, notifyURL, text string) error
}

DeviceLoginNotifier sends one plain-text notice through a channel. *alerting.DeployDispatcher satisfies it.

type DockerDiskUsager ¶

type DockerDiskUsager interface {
	DiskUsage(ctx context.Context) (docker.DiskUsage, error)
}

DockerDiskUsager is the surface GET /api/v1/system/status needs for Docker's own storage accounting (images/containers/volumes/build cache), a materially different number from DataDirTotalBytes/ DataDirFreeBytes above: see docker.DiskUsage's own doc comment for why. Narrow and single-purpose like DockerPinger, not docker.Runtime, same "adding to Runtime would ripple into 6+ fakes for one read-only signal" reasoning DockerPinger's own doc comment gives. *docker.Client satisfies this structurally via the DiskUsage method added directly to that concrete type (internal/docker/prune.go).

type DockerGuardController ¶ added in v0.2.0

type DockerGuardController interface {
	Status() dockerguard.Status
	SetMode(mode dockerguard.Mode, actor string, now time.Time) (dockerguard.Status, error)
}

DockerGuardController is the Docker API guard as the API sees it; *dockerguard.Controller satisfies it.

type DockerHubClient ¶ added in v0.2.0

type DockerHubClient interface {
	SearchRepositories(ctx context.Context, query string, pageSize int) ([]dockerhub.Repository, error)
	ListTags(ctx context.Context, namespace, repository string, pageSize int) ([]dockerhub.Tag, error)
}

DockerHubClient is the surface GET /api/v1/dockerhub/search and GET /api/v1/dockerhub/repositories/{namespace}/{repo}/tags need: query Docker Hub's public, unauthenticated Hub API for repository search and tag listing. *dockerhub.Client satisfies this structurally.

type DockerPinger ¶

type DockerPinger interface {
	Ping(ctx context.Context) error
}

DockerPinger is the surface GET /api/v1/system/status needs to report Docker daemon connectivity: a liveness check, nothing else. *docker.Client satisfies this structurally via the Ping method added directly to that concrete type (internal/docker/client.go), not through docker.Runtime: Runtime has 6+ fake implementations across internal/reconcile's controllers and internal/agent's test files, plus the real client and the agent-side remote transport, so adding a method there for the sake of this one read-only health signal would ripple into all of them. This narrow interface is the actual consumer-defined boundary instead, the same shape SecretSetter and TelemetryQuerier already establish for their own single-purpose surfaces.

type DockerPruner ¶

type DockerPruner interface {
	Prune(ctx context.Context, keep []string) docker.PruneResult
}

DockerPruner is the surface POST /api/v1/system/prune needs: run every cleanup stage internal/docker/prune.go supports and report what happened. Unlike DockerDiskUsager (a read), this is a destructive-ish action, gated at AbilityRoot the same way handleDrainNode is (see router.go's own route registration), so it gets its own interface rather than being folded into DockerDiskUsager: a read and a destructive action deserve independently swappable seams, the same split Builder/ImageLister already have. *docker.Client satisfies this structurally via the Prune method (internal/docker/prune.go).

type DoctorIngressEdge ¶ added in v0.2.0

type DoctorIngressEdge struct {
	SocketActivation bool
	Hardening        bool
	TrustedProxies   int
	MaxBodyBytes     int64
	RetryWindow      time.Duration
}

DoctorIngressEdge is the ingress edge policy GET /api/v1/system/doctor reports.

type Document ¶

type Document struct {
	Statement []Statement `json:"Statement"`
}

Document is the whole parsed policy body stored as JSON in store.Policy.Document.

func ParseDocument ¶

func ParseDocument(raw string) (*Document, error)

ParseDocument unmarshals and validates a policy document. A document must have at least one statement, every statement must have an Effect of Allow or Deny, at least one Action (each either "*" or a known ability string from validAbilities), and at least one Resource (each either "*" or a non-empty resource identifier).

type DomainBasicAuthSecrets ¶

type DomainBasicAuthSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

DomainBasicAuthSecrets is the surface these handlers need from internal/secrets.Manager for a domain's basic-auth password: set it (PUT), check whether one exists (GET's has_password, and PUT's "already set" check for the omitted-password case), and clear it (DELETE). *secrets.Manager satisfies this structurally, the same narrow shape CloudflareTunnelSecrets already establishes.

type DomainBasicAuthStore ¶

type DomainBasicAuthStore interface {
	GetDomainBasicAuth(ctx context.Context, domain string) (store.DomainBasicAuth, bool, error)
	SetDomainBasicAuth(ctx context.Context, domain, username string) error
	DeleteDomainBasicAuth(ctx context.Context, domain string) error
	// ListDomainBasicAuth backs GET /api/v1/domains's has_basic_auth
	// flag: one bulk read instead of a per-domain GetDomainBasicAuth
	// call, avoiding an N+1 query over the domain list.
	ListDomainBasicAuth(ctx context.Context) ([]store.DomainBasicAuth, error)
}

DomainBasicAuthStore is the store surface GET/PUT/DELETE .../domains/{domain}/auth need: the username claimed for a domain, always set, same "core Store interface" shape as DomainStore above.

type DomainErrorPagesStore ¶ added in v0.2.0

type DomainErrorPagesStore interface {
	ListDomainErrorPages(ctx context.Context, domain string) ([]store.DomainErrorPage, error)
	SetDomainErrorPage(ctx context.Context, domain string, statusCode int, body string) error
	DeleteDomainErrorPage(ctx context.Context, domain string, statusCode int) error
	DeleteDomainErrorPages(ctx context.Context, domain string) error
}

DomainErrorPagesStore is the store surface GET/PUT/DELETE .../domains/{domain}/error-pages need: a domain's status-code-to-HTML mappings, always set, same "core Store interface" shape as DomainRedirectStore above; no secrets dependency either.

type DomainMaintenanceStore ¶

type DomainMaintenanceStore interface {
	GetDomainMaintenance(ctx context.Context, domain string) (bool, error)
	SetDomainMaintenance(ctx context.Context, domain string) error
	DeleteDomainMaintenance(ctx context.Context, domain string) error
	// ListDomainMaintenance backs GET /api/v1/domains's
	// maintenance_enabled flag: one bulk read instead of a per-domain
	// GetDomainMaintenance call, avoiding an N+1 query over the domain
	// list.
	ListDomainMaintenance(ctx context.Context) ([]string, error)
}

DomainMaintenanceStore is the store surface GET/PUT/DELETE .../domains/{domain}/maintenance need: whether a domain currently has maintenance mode enabled, always set, same "core Store interface" shape as DomainBasicAuthStore above.

type DomainRedirectStore ¶ added in v0.2.0

type DomainRedirectStore interface {
	GetDomainRedirect(ctx context.Context, domain string) (store.DomainRedirect, bool, error)
	SetDomainRedirect(ctx context.Context, domain, targetURL string, statusCode int) error
	DeleteDomainRedirect(ctx context.Context, domain string) error
	// ListDomainRedirects backs GET /api/v1/domains's has_redirect flag:
	// one bulk read instead of a per-domain GetDomainRedirect call,
	// avoiding an N+1 query over the domain list.
	ListDomainRedirects(ctx context.Context) ([]store.DomainRedirect, error)
}

DomainRedirectStore is the store surface GET/PUT/DELETE .../domains/{domain}/redirect need: an opt-in redirect to an arbitrary target URL for a domain, always set, same "core Store interface" shape as DomainMaintenanceStore above; like that store, there is no secrets dependency at all, since neither the target URL nor the status code is credential material.

type DomainSearchVisibilityStore ¶ added in v0.2.0

type DomainSearchVisibilityStore interface {
	IsDomainHidden(ctx context.Context, domain string) (bool, error)
	SetDomainHidden(ctx context.Context, domain string, hidden bool) error
}

DomainSearchVisibilityStore is the store surface for .../domains/{domain}/search-visibility.

type DomainStore ¶

type DomainStore interface {
	ListServiceDomains(ctx context.Context) ([]store.ServiceDomain, error)
}

DomainStore is the store surface GET /api/v1/domains needs: every domain currently claimed by a service, for the centralized cross-app domains list (web/src/routes/domains). Reuses internal/store's existing service_domains table (kept in sync by SaveDesiredService on every write, see that method's own doc comment); this is a plain read of already-tracked state, not a new tracking mechanism.

type DomainTLSCertSecrets ¶

type DomainTLSCertSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	DeleteAll(ctx context.Context, serviceName string) error
}

DomainTLSCertSecrets is the surface these handlers need from internal/secrets.Manager for a domain's certificate and private key: set both (PUT) and clear both (DELETE). *secrets.Manager satisfies this structurally, the same narrow shape DomainBasicAuthSecrets already establishes.

type DomainTLSCertStore ¶

type DomainTLSCertStore interface {
	GetDomainTLSCert(ctx context.Context, domain string) (store.DomainTLSCert, bool, error)
	SetDomainTLSCert(ctx context.Context, domain string, uploadedAt, expiresAt time.Time) error
	DeleteDomainTLSCert(ctx context.Context, domain string) error
	// ListDomainTLSCerts backs handleListCertificates' Source field (api/
	// certificates.go): every domain with a custom cert on file, so the
	// certificate center page can tell "custom" apart from "acme" per row.
	ListDomainTLSCerts(ctx context.Context) ([]store.DomainTLSCert, error)
}

DomainTLSCertStore is the store surface GET/PUT/DELETE .../domains/{domain}/tls-cert need: the upload/expiry metadata for a domain's BYO certificate, same "core Store interface" shape as DomainBasicAuthStore.

type DomainTrafficPolicyStore ¶ added in v0.2.0

type DomainTrafficPolicyStore interface {
	GetDomainTrafficPolicy(ctx context.Context, domain, kind string) (store.DomainTrafficPolicy, bool, error)
	ListDomainTrafficPoliciesFor(ctx context.Context, domain string) ([]store.DomainTrafficPolicy, error)
	SetDomainTrafficPolicy(ctx context.Context, domain, kind string, spec []byte) error
	DeleteDomainTrafficPolicy(ctx context.Context, domain, kind string) error
}

DomainTrafficPolicyStore is the store surface for the per-domain traffic controls (headers, forwarders, geo, cache, redirects).

type DomainWAFStore ¶ added in v0.2.0

type DomainWAFStore interface {
	GetDomainWAF(ctx context.Context, domain string) (store.DomainWAF, bool, error)
	SetDomainWAF(ctx context.Context, domain string, wafEnabled bool, mode string, rateLimitRPS, rateLimitBurst int) error
	DeleteDomainWAF(ctx context.Context, domain string) error
	// ListDomainWAF backs GET /api/v1/domains's waf_enabled flag: one
	// bulk read instead of a per-domain GetDomainWAF call, avoiding an
	// N+1 query over the domain list.
	ListDomainWAF(ctx context.Context) ([]store.DomainWAF, error)
}

DomainWAFStore is the store surface GET/PUT/DELETE .../domains/{domain}/waf need: opt-in WAF and rate-limit settings for a domain, always set, same "core Store interface" shape as DomainMaintenanceStore above; like that store, there is no secrets dependency at all, since neither setting involves credential material.

type Effect ¶

type Effect string

Effect is a statement's outcome when it matches: Allow or Deny, exactly AWS IAM's own two-value vocabulary.

const (
	EffectAllow Effect = "Allow"
	EffectDeny  Effect = "Deny"
)

EffectAllow and EffectDeny are the only two valid Statement.Effect values.

type EmailSecretsStore ¶

type EmailSecretsStore interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
}

EmailSecretsStore is the surface the email settings handlers need from internal/secrets.Manager. *secrets.Manager satisfies this structurally.

type EmailSettingsStore ¶

type EmailSettingsStore interface {
	GetEmailSettings(ctx context.Context) (store.EmailSettings, error)
	UpdateEmailSettings(ctx context.Context, s store.EmailSettings) error
}

EmailSettingsStore is the store surface GET/PUT /api/v1/settings/email need: the single platform-wide row, always present, the same shape IngressSettingsStore has for its own row.

type EnvironmentStore ¶

type EnvironmentStore interface {
	SaveEnvironment(ctx context.Context, e store.Environment) error
	GetEnvironment(ctx context.Context, id string) (store.Environment, error)
	GetEnvironmentsByIDs(ctx context.Context, ids []string) (map[string]store.Environment, error)
	ListEnvironmentsByProject(ctx context.Context, projectID string) ([]store.Environment, error)
	DeleteEnvironment(ctx context.Context, id string) error
	SetEnvironmentProtected(ctx context.Context, id string, protected bool) error
	UpdateEnvironment(ctx context.Context, id string, patch store.EnvironmentPatch) error
	ListAllEnvironments(ctx context.Context) ([]store.EnvironmentWithCounts, error)
	EnvironmentMemberCounts(ctx context.Context, envID string) (apps, databases int, err error)
	MoveEnvironmentMembers(ctx context.Context, fromID, toID string) error
	EnvironmentOfDatabase(ctx context.Context, databaseName string) (*store.EnvironmentRef, error)
	SetServiceEnvironment(ctx context.Context, serviceName, envID string) error
	// SetEnvironmentEnvVars/ListEnvironmentEnvVars back GET/PUT
	// /api/v1/environments/{id}/env (environment_env.go): shared env vars
	// every service tagged with this environment inherits, sitting
	// between ProjectStore's own project_env_vars tier and a service's
	// own env (internal/reconcile/application's resolveEnv), full-replace
	// on write, mirroring SetOrganizationEnvVars/ListOrganizationEnvVars.
	SetEnvironmentEnvVars(ctx context.Context, environmentID string, vars map[string]string) error
	ListEnvironmentEnvVars(ctx context.Context, environmentID string) (map[string]string, error)
	// ListEnvironmentEnvVarsDetailed/SetEnvironmentSecretEnvVar/
	// DeleteEnvironmentSecretEnvVar/ListEnvironmentSecretEnvKeys back GET
	// /api/v1/environments/{id}/env/all and the secret-var sub-resource
	// routes (environment_env.go), mirroring ProjectStore/
	// OrganizationStore's own secret-capable extension.
	ListEnvironmentEnvVarsDetailed(ctx context.Context, environmentID string) ([]store.SharedEnvVar, error)
	SetEnvironmentSecretEnvVar(ctx context.Context, environmentID, key string) error
	DeleteEnvironmentSecretEnvVar(ctx context.Context, environmentID, key string) error
	ListEnvironmentSecretEnvKeys(ctx context.Context, environmentID string) ([]string, error)
}

EnvironmentStore is the store surface the environments handlers need.

type EscrowMaterialReader ¶ added in v0.2.0

type EscrowMaterialReader func() (cpbackup.EscrowMaterial, error)

EscrowMaterialReader returns the master key and the other recovery files an escrow bundle protects. It is called only to build a bundle and its result is never logged.

type ExposureStore ¶ added in v0.2.0

type ExposureStore interface {
	SaveExposureRestriction(ctx context.Context, r store.ExposureRestriction) error
	ListExposureRestrictions(ctx context.Context) ([]store.ExposureRestriction, error)
	DeleteExposureRestriction(ctx context.Context, port int, protocol string) error
}

ExposureStore is the store surface the exposure handlers need.

type ExternalDatabaseStore ¶ added in v0.2.0

type ExternalDatabaseStore interface {
	CreateExternalDatabase(ctx context.Context, d store.ExternalDatabase) error
	UpdateExternalDatabase(ctx context.Context, d store.ExternalDatabase) error
	GetExternalDatabase(ctx context.Context, name string) (*store.ExternalDatabase, error)
	ListExternalDatabases(ctx context.Context) ([]store.ExternalDatabase, error)
	DeleteExternalDatabase(ctx context.Context, name string) error
	SetExternalDatabaseHealth(ctx context.Context, name, status, reason string, latencyMs int, checkedAt time.Time) error
}

ExternalDatabaseStore is the store surface for databases this platform connects to but does not run.

type FeatureFlagStore ¶

type FeatureFlagStore interface {
	SaveFeatureFlag(ctx context.Context, f store.FeatureFlag) error
	GetFeatureFlag(ctx context.Context, id string) (store.FeatureFlag, error)
	GetFeatureFlagByKey(ctx context.Context, key string) (store.FeatureFlag, error)
	ListFeatureFlagsForService(ctx context.Context, serviceName string) ([]store.FeatureFlag, error)
	UpdateFeatureFlag(ctx context.Context, id, name, description string, enabled bool, rolloutPercentage int, updatedAt time.Time) error
	DeleteFeatureFlag(ctx context.Context, id string) error
}

FeatureFlagStore is the store surface the feature flag handlers need, mirroring ScheduledTaskStore's own shape for a different child resource.

type FirewallRuleStore ¶ added in v0.2.0

type FirewallRuleStore interface {
	SaveFirewallRule(ctx context.Context, r store.FirewallRule) error
	ListFirewallRules(ctx context.Context) ([]store.FirewallRule, error)
	DeleteFirewallRule(ctx context.Context, id string) error
}

FirewallRuleStore is the store surface the firewall rule handlers need.

type ForgeDeploymentStore ¶ added in v0.2.0

type ForgeDeploymentStore interface {
	CreateForgeDeployment(ctx context.Context, d store.ForgeDeployment) error
	SetForgeDeploymentState(ctx context.Context, id, state, warning string, at time.Time) error
	ListForgeDeploymentsByState(ctx context.Context, app, environment, state, excludeID string) ([]store.ForgeDeployment, error)
}

ForgeDeploymentStore persists the deployments created on a git forge. *store.DB satisfies it.

type GPUHostDiagnoser ¶ added in v0.2.0

type GPUHostDiagnoser func(ctx context.Context) gpu.HostDiagnosis

GPUHostDiagnoser inspects the control plane host's GPU stack in detail: driver, toolkit, CDI specs and how containers can attach GPUs.

type GitHubAppClient ¶

type GitHubAppClient interface {
	CheckInstanceReachable(ctx context.Context, instanceURL string) error
	ExchangeManifestCode(ctx context.Context, instanceURL, code string) (githubapp.Credentials, error)
	GetInstallation(ctx context.Context, instanceURL, appJWT string, installationID int64) (githubapp.InstallationInfo, error)
	MintInstallationToken(ctx context.Context, instanceURL, appJWT string, installationID int64) (githubapp.InstallationToken, error)
	ListInstallationRepos(ctx context.Context, instanceURL, token string) ([]githubapp.Repo, error)
	GetRepo(ctx context.Context, instanceURL, token, owner, repo string) (githubapp.Repo, error)
	ListBranches(ctx context.Context, instanceURL, token, owner, repo string) ([]githubapp.Branch, error)
	CreateRepoWebhook(ctx context.Context, instanceURL, token, owner, repo, hookURL, secret string) error
	CreateIssueComment(ctx context.Context, instanceURL, token, owner, repo string, number int, body string) (int64, error)
	ListIssueComments(ctx context.Context, instanceURL, token, owner, repo string, number int) ([]gitprovider.Comment, error)
	UpdateIssueComment(ctx context.Context, instanceURL, token, owner, repo string, commentID int64, body string) error
	CreateCommitStatus(ctx context.Context, instanceURL, token, owner, repo, sha string, state githubapp.CommitStatusState, targetURL, description, statusContext string) error
}

GitHubAppClient is the surface internal/api needs from internal/githubapp.Client: manifest code exchange, installation lookup and token minting, repo/branch listing, repo webhook registration, and posting a preview deploy's own PR comment/commit status (preview_environments_github.go). *githubapp.Client satisfies this structurally; tests substitute a hand-written fake (github_app_test.go), the same "narrow, consumer-defined interface" shape every other external-system boundary in this package uses (Builder, DockerPinger, BackupRunner).

type GitHubAppSecrets ¶

type GitHubAppSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

GitHubAppSecrets is the surface the GitHub App handlers need from internal/secrets.Manager: both SetValue (storing client_secret/ webhook_secret/private_key right after a successful code exchange) and Resolve (decrypting the private key back out, in memory only, immediately before signing a JWT). A different shape from BackupSecretsSetter (set-only) and internal/backup.SecretsResolver (resolve-only): this is the one credential-bearing feature in this codebase that both writes and reads back through internal/secrets from inside internal/api itself, because minting a fresh installation token on every repo/branch-listing call (see github_app_repos.go) needs the private key synchronously in the same request, not on a separate worker path the way internal/backup.Runner's own Resolve calls are. *secrets.Manager satisfies this structurally.

type GitHubAppStore ¶

type GitHubAppStore interface {
	GetGitHubAppConnection(ctx context.Context) (store.GitHubAppConnection, error)
	SaveGitHubAppConnection(ctx context.Context, c store.GitHubAppConnection) error
	UpdateGitHubAppInstallation(ctx context.Context, installationID int64, accountLogin string) error
	DeleteGitHubAppConnection(ctx context.Context) error
	UpsertGitHubAppInstallation(ctx context.Context, installationID int64, accountLogin, accountType string) error
	ListGitHubAppInstallations(ctx context.Context) ([]store.GitHubAppInstallation, error)
	DeleteGitHubAppInstallation(ctx context.Context, id int64) error
}

GitHubAppStore is the store surface the GitHub App connection handlers need. *store.DB satisfies this structurally, part of the core Store interface (github_app_connections always exists as a table even when empty, the same "core store, not an optional plug-in" shape ingressSettings/domains already establish).

type GitLabAppClient ¶

type GitLabAppClient interface {
	ExchangeCode(ctx context.Context, instanceURL, clientID, clientSecret, redirectURI, code string) (gitlabapp.Tokens, error)
	RefreshToken(ctx context.Context, instanceURL, clientID, clientSecret, refreshToken string) (gitlabapp.Tokens, error)
	ListProjects(ctx context.Context, instanceURL, accessToken string) ([]gitlabapp.Project, error)
	GetProject(ctx context.Context, instanceURL, accessToken string, projectID int64) (gitlabapp.Project, error)
	ListBranches(ctx context.Context, instanceURL, accessToken string, projectID int64) ([]gitlabapp.Branch, error)
	CreateProjectWebhook(ctx context.Context, instanceURL, accessToken string, projectID int64, hookURL, secretToken string) error
	CreateMergeRequestNote(ctx context.Context, instanceURL, accessToken, projectPath string, mrIID int, body string) (int64, error)
	ListMergeRequestNotes(ctx context.Context, instanceURL, accessToken, projectPath string, mrIID int) ([]gitprovider.Comment, error)
	UpdateMergeRequestNote(ctx context.Context, instanceURL, accessToken, projectPath string, mrIID int, noteID int64, body string) error
	CreateCommitStatus(ctx context.Context, instanceURL, accessToken, projectPath, sha string, state gitlabapp.CommitState, targetURL, description, name string) error
}

GitLabAppClient is the surface internal/api needs from internal/gitlabapp.Client: OAuth code exchange/refresh, and project listing/lookup/webhook registration once connected. *gitlabapp.Client satisfies this structurally; tests substitute a hand-written fake.

type GitLabAppSecrets ¶

type GitLabAppSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

GitLabAppSecrets is the surface the GitLab App handlers need from internal/secrets.Manager: client_secret is set once at connect time and resolved back out on every OAuth token exchange/refresh and project API call; access_token/refresh_token/token_expires_at are written and re-read on the same schedule by gitlabAccessToken. The same "this feature both writes and reads back through internal/secrets" shape GitHubAppSecrets's own doc comment describes.

type GitLabAppStore ¶

type GitLabAppStore interface {
	GetGitLabAppConnection(ctx context.Context) (store.GitLabAppConnection, error)
	SaveGitLabAppConnection(ctx context.Context, c store.GitLabAppConnection) error
	DeleteGitLabAppConnection(ctx context.Context) error
}

GitLabAppStore is the store surface the GitLab App connection handlers need. *store.DB satisfies this structurally, the same "core store" shape GitHubAppStore's own doc comment establishes.

type GitSourceSecrets ¶

type GitSourceSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
}

GitSourceSecrets is the surface a git source's connect flow and the git-push webhook route (git_webhook.go) both need from internal/secrets.Manager: unlike SecretSetter and BackupSecretsSetter (write-only, a secret's value never resolved back through internal/api at all), this package genuinely does call Resolve, but only for its own server-side use, HMAC-verifying an incoming webhook and authenticating a git clone, never to populate an HTTP response body. See handleSetGitSource's and handleGitPushWebhook's own doc comments for that boundary; *secrets.Manager satisfies this structurally.

type GitSourceStore ¶

type GitSourceStore interface {
	SaveGitSource(ctx context.Context, g store.GitSource) error
	GetGitSource(ctx context.Context, serviceName string) (*store.GitSource, error)
	DeleteGitSource(ctx context.Context, serviceName string) error
	// ListGitSources backs GET /api/v1/apps/git-sources: every connected
	// source's service name, repo URL and branch, filtered to the
	// caller's visible apps by handleListGitSources itself (callerAppVisibility),
	// the same shape handleListApps already uses for its own bulk list.
	ListGitSources(ctx context.Context) ([]store.GitSourceSummary, error)
	// SetGitSourcePreviewEnabled backs PUT
	// /api/v1/apps/{name}/preview-settings (preview_environments_handlers.go):
	// the opt-in toggle for preview environments per pull request.
	SetGitSourcePreviewEnabled(ctx context.Context, serviceName string, enabled bool) error
	// SetGitSourcePostPRComments backs the same PUT
	// /api/v1/apps/{name}/preview-settings route: the opt-in toggle for
	// posting a GitHub PR comment/commit status about a preview deploy.
	SetGitSourcePostPRComments(ctx context.Context, serviceName string, enabled bool) error
	// SetGitSourceDeploySettings backs PUT
	// /api/v1/apps/{name}/git-source/deploy-settings: push path filters
	// and forge status reporting.
	SetGitSourceDeploySettings(ctx context.Context, serviceName string, paths, pathsIgnore []string, reportStatus bool) error
	// SetGitSourceBuild backs PUT /api/v1/apps/{name}/git-source/build.
	SetGitSourceBuild(ctx context.Context, serviceName, buildType, buildPath, baseDirectory string) error
}

GitSourceStore is the store surface the git source handlers need. *store.DB satisfies this structurally.

type GiteaAppClient ¶ added in v0.2.0

type GiteaAppClient interface {
	ExchangeCode(ctx context.Context, instanceURL, clientID, clientSecret, redirectURI, code string) (giteaapp.Tokens, error)
	RefreshToken(ctx context.Context, instanceURL, clientID, clientSecret, refreshToken string) (giteaapp.Tokens, error)
	ListRepos(ctx context.Context, instanceURL, accessToken string) ([]giteaapp.Repo, error)
	GetRepo(ctx context.Context, instanceURL, accessToken, fullName string) (giteaapp.Repo, error)
	ListBranches(ctx context.Context, instanceURL, accessToken, fullName string) ([]giteaapp.Branch, error)
	CreateRepoWebhook(ctx context.Context, instanceURL, accessToken, fullName, hookURL, secret string) error
	CreateIssueComment(ctx context.Context, instanceURL, accessToken, fullName string, number int, body string) (int64, error)
	ListIssueComments(ctx context.Context, instanceURL, accessToken, fullName string, number int) ([]gitprovider.Comment, error)
	UpdateIssueComment(ctx context.Context, instanceURL, accessToken, fullName string, commentID int64, body string) error
	CreateCommitStatus(ctx context.Context, instanceURL, accessToken, fullName, sha string, state giteaapp.CommitStatusState, targetURL, description, statusContext string) error
}

GiteaAppClient is the surface internal/api needs from internal/giteaapp.Client: OAuth code exchange/refresh, and repo listing/lookup/webhook registration once connected. *giteaapp.Client satisfies this structurally; tests substitute a hand-written fake.

type GiteaAppSecrets ¶ added in v0.2.0

type GiteaAppSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

GiteaAppSecrets is the surface the Gitea App handlers need from internal/secrets.Manager: client_secret is set once at connect time and resolved back out on every OAuth token exchange/refresh and repo/branch API call; access_token/refresh_token/token_expires_at are written and re-read on the same schedule by giteaAccessToken. The same "this feature both writes and reads back through internal/secrets" shape GitLabAppSecrets's own doc comment describes.

type GiteaAppStore ¶ added in v0.2.0

type GiteaAppStore interface {
	GetGiteaAppConnection(ctx context.Context) (store.GiteaAppConnection, error)
	SaveGiteaAppConnection(ctx context.Context, c store.GiteaAppConnection) error
	DeleteGiteaAppConnection(ctx context.Context) error
}

GiteaAppStore is the store surface the Gitea App connection handlers need. *store.DB satisfies this structurally, the same "core store" shape GitLabAppStore's own doc comment establishes.

type HookRunStore ¶

type HookRunStore interface {
	GetHookRuns(ctx context.Context, serviceName string) ([]store.HookRun, error)
}

HookRunStore is the store surface GET /api/v1/apps/{name}/hook-runs (apps_hooks.go) needs: the most recent outcome of each of a service's pre/post-deploy hooks (internal/reconcile/application's own HookRunRecorder, migrations/0083_service_hook_runs.sql). Always set, the same "core Store interface, not an optional plug-in" shape domainMaintenance above establishes.

type ImageAutoUpdateStore ¶ added in v0.2.0

type ImageAutoUpdateStore interface {
	SetImageAutoUpdate(ctx context.Context, serviceName string, enabled bool) error
	GetImageAutoUpdate(ctx context.Context, serviceName string) (store.ImageAutoUpdate, error)
	RecordImageAutoUpdateCheck(ctx context.Context, serviceName, result string, at time.Time) error
	SetImageAutoUpdateWebhookHash(ctx context.Context, serviceName, hash string) error
}

ImageAutoUpdateStore is the store surface for per-app image auto-update.

type ImageLister ¶

type ImageLister interface {
	ListImages(ctx context.Context, repo string) ([]docker.ImageInfo, error)
}

ImageLister is the surface GET /api/v1/apps/{name}/images needs: discover previously-built tags under a repo, so the deploy trigger form (web/src/components/DeployTriggerForm.tsx) can offer a dropdown instead of forcing an operator to hand-type a full image reference every time. Unlike DockerPinger above, this mirrors an existing docker.Runtime method (internal/docker/runtime.go) exactly rather than inventing a narrower one: ListImages is already the consumer-defined boundary every reconciler controller depends on for rollback candidate discovery, so redeclaring it here (structurally satisfied by *docker.Client, same as Runtime) avoids a second, divergent surface for the same one capability. *docker.Client satisfies this structurally.

type IngressPortOwner ¶ added in v0.2.0

type IngressPortOwner interface {
	OwnsPort(port int) bool
}

IngressPortOwner reports whether this control plane's own embedded ingress (internal/ingress.Driver) currently has a listener bound to port. *ingress.Driver satisfies this.

type IngressSettingsStore ¶

type IngressSettingsStore interface {
	GetIngressSettings(ctx context.Context) (store.IngressSettings, error)
	UpdateIngressSettings(ctx context.Context, s store.IngressSettings) error
	GetDashboardURL(ctx context.Context) (string, error)
	SetDashboardURL(ctx context.Context, u string) error
}

IngressSettingsStore is the store surface the ingress settings handlers need. *store.DB satisfies this structurally, the same consumer-defined interface convention every other Store sub-interface in this package follows (see CertStore, StaticSiteStore).

type InviteStore ¶

type InviteStore interface {
	SaveInvite(ctx context.Context, inv store.Invite) error
	GetInviteByHash(ctx context.Context, hash string) (*store.Invite, error)
	GetInviteByID(ctx context.Context, id string) (*store.Invite, error)
	ListPendingInvites(ctx context.Context) ([]store.Invite, error)
	RevokeInvite(ctx context.Context, id string) error
	ClaimInvite(ctx context.Context, id string) error
}

InviteStore is the store surface the team-invite flow needs: always set, part of the core Store interface, same shape as PasswordResetTokenStore above.

type LBAdminStateStore ¶ added in v0.2.0

type LBAdminStateStore interface {
	SetLBUpstreamAdminState(ctx context.Context, service string, replica int, state string) error
	ListLBUpstreamAdminStates(ctx context.Context) (map[string]map[int]string, error)
}

LBAdminStateStore persists per-upstream admin state. *store.DB satisfies this; a LoadBalancerStore that does not implement it serves no admin state.

type LoadBalancerStore ¶ added in v0.2.0

type LoadBalancerStore interface {
	GetServiceLoadBalancer(ctx context.Context, service string) (string, bool, error)
	SetServiceLoadBalancer(ctx context.Context, service, configJSON string) error
	DeleteServiceLoadBalancer(ctx context.Context, service string) error
	ListLoadBalancerRows(ctx context.Context) ([]store.LoadBalancerRow, error)
}

LoadBalancerStore is the persistence surface for per-app load balancer config. *store.DB satisfies this.

type LocalContainerLister ¶ added in v0.2.0

type LocalContainerLister interface {
	ListLocalContainers(ctx context.Context) ([]docker.LocalContainer, error)
}

LocalContainerLister is implemented by the real Docker client. A runtime that is not one cannot discover local source containers.

type LogArchiveStore ¶ added in v0.2.0

type LogArchiveStore interface {
	UpsertLogArchivePolicy(ctx context.Context, p store.LogArchivePolicy) error
	GetLogArchivePolicy(ctx context.Context, appName string) (store.LogArchivePolicy, error)
	ListLogArchivePolicies(ctx context.Context) ([]store.LogArchivePolicy, error)
	DeleteLogArchivePolicy(ctx context.Context, appName string) error
	CountLogArchivePoliciesForTarget(ctx context.Context, targetID string) (int, error)
	ListLogArchiveRuns(ctx context.Context, appName string, all bool, limit int) ([]store.LogArchiveRun, error)
}

LogArchiveStore persists log archive policies and run history.

type LogArchiver ¶ added in v0.2.0

type LogArchiver interface {
	StartDump(ctx context.Context, req objectstore.DumpRequest) (store.LogArchiveRun, error)
}

LogArchiver starts manual log dumps.

type LoginCodeStore ¶ added in v0.2.0

type LoginCodeStore interface {
	CreateLoginCodeChallenge(ctx context.Context, c store.LoginCodeChallenge) error
	GetLiveLoginCodeByBrowser(ctx context.Context, browserHash string, now time.Time) (store.LoginCodeChallenge, error)
	ReserveLoginCodeAttempt(ctx context.Context, id string, maxAttempts int) (int, bool, error)
	LockLoginCode(ctx context.Context, id string, now time.Time) (bool, error)
	RedeemLoginCode(ctx context.Context, id string, now time.Time) (bool, error)
	ListLiveLoginCodesForUser(ctx context.Context, userID string, now time.Time) ([]store.LoginCodeChallenge, error)
	ClaimLapsedLoginCodes(ctx context.Context, now time.Time, limit int) ([]store.LoginCodeChallenge, error)
	PruneLoginCodes(ctx context.Context, cutoff time.Time) error
	CreateLoginApproval(ctx context.Context, a store.LoginApproval) error
	GetLoginApproval(ctx context.Context, id string) (store.LoginApproval, error)
	GetLoginApprovalByBrowser(ctx context.Context, browserHash string) (store.LoginApproval, error)
	DecideLoginApproval(ctx context.Context, id, userID string, approve bool, decidedBy string, now time.Time) (bool, error)
	ConsumeLoginApproval(ctx context.Context, id string, now time.Time) (bool, error)
	ListPendingLoginApprovalsForUser(ctx context.Context, userID string, now time.Time) ([]store.LoginApproval, error)
	ClaimLapsedLoginApprovals(ctx context.Context, now time.Time, limit int) ([]store.LoginApproval, error)
	CreateTrustedDevice(ctx context.Context, d store.TrustedDevice) error
	GetTrustedDeviceByHash(ctx context.Context, tokenHash string, now time.Time) (store.TrustedDevice, error)
	TouchTrustedDevice(ctx context.Context, id string, now time.Time) error
	ListTrustedDevices(ctx context.Context, userID string, now time.Time) ([]store.TrustedDevice, error)
	RevokeTrustedDevice(ctx context.Context, userID, id string, now time.Time) (bool, error)
	RevokeAllTrustedDevices(ctx context.Context, userID string, now time.Time) (int, error)
	ExtendTrustedDevice(ctx context.Context, id string, expiresAt, now time.Time) error
	PruneTrustedDevices(ctx context.Context, cutoff time.Time) error
	SupersedeLoginApprovals(ctx context.Context, userID string, now time.Time) ([]store.LoginApproval, error)
	ReplaceLoginApproval(ctx context.Context, a store.LoginApproval) ([]store.LoginApproval, error)
	ExpireLoginCode(ctx context.Context, id string, now time.Time) (bool, error)
	LockLoginCodesForUser(ctx context.Context, userID string, now time.Time) ([]string, error)
	GetCodeLoginSettings(ctx context.Context) (store.CodeLoginSettings, bool, error)
	SaveCodeLoginSettings(ctx context.Context, s store.CodeLoginSettings) error
}

LoginCodeStore is the store surface sign in with a code, new-device approval and trusted devices need.

type MajorUpgradeStore ¶ added in v0.2.0

type MajorUpgradeStore interface {
	ListMajorUpgrades(ctx context.Context, databaseName string) ([]store.MajorUpgrade, error)
	GetMajorUpgrade(ctx context.Context, id string) (store.MajorUpgrade, error)
	ListMajorUpgradeSnapshots(ctx context.Context) ([]store.MajorUpgrade, error)
}

MajorUpgradeStore is the store surface the major upgrade routes read.

type MajorUpgrader ¶ added in v0.2.0

type MajorUpgrader interface {
	RunMajorUpgrade(ctx context.Context, historyID, databaseName, toVersion string) error
	Rollback(ctx context.Context, upgradeID string) error
	DiscardSnapshot(ctx context.Context, upgradeID string) error
}

MajorUpgrader runs guarded major version upgrades (internal/backup's MajorUpgradeRunner).

type MasterKeyRotator ¶

type MasterKeyRotator interface {
	RotateMasterKey(ctx context.Context, newMasterKey string) (rotatedAt time.Time, err error)
	GetMasterKeyRotatedAt(ctx context.Context) (rotatedAt time.Time, ok bool, err error)
}

MasterKeyRotator is the surface POST /api/v1/system/master-key/rotate and the doctor's rotation-age check need from internal/secrets.Manager: rotate every stored DEK to a new master key (given as its serialized string, so this interface never imports internal/secrets' own MasterKey type), and report when that last succeeded. A distinct interface from SecretSetter above since a token scoped to AbilityWrite (SecretSetter's own gate) has no business rotating the one key every other secret in this control plane depends on; this is gated at AbilityRoot instead (see routes.go).

type MeshKeyRotator ¶ added in v0.2.0

type MeshKeyRotator interface {
	RotateKey(ctx context.Context, nodeID string) (network.RotationResult, error)
	RotationStatusFor(nodeID string) (network.RotationStatus, bool)
}

MeshKeyRotator is the narrow surface this package needs from *network.Coordinator for rotation: rotate one node's key, and read back what Coordinator itself knows about in-flight rotations. *network.Coordinator satisfies this structurally.

type MeshStatusProvider ¶ added in v0.2.0

type MeshStatusProvider interface {
	Status(ctx context.Context) (network.Status, error)
}

MeshStatusProvider is the narrow surface this package needs from this node's live network.Mesh: exactly Status, never Apply or Close, since this package only ever reads the mesh, never drives it (the reconcile loop owns that). *network.Device, *network.Disabled, and any test fake implementing Status alone all satisfy this.

type MigrationHubStore ¶ added in v0.2.0

type MigrationHubStore interface {
	CreateMigrationSession(ctx context.Context, s store.MigrationSession, items []store.MigrationItem) error
	GetMigrationSession(ctx context.Context, id string) (store.MigrationSession, error)
	ListMigrationSessions(ctx context.Context) ([]store.MigrationSession, error)
	SetMigrationStep(ctx context.Context, id, step string, now time.Time) error
	DeleteMigrationSession(ctx context.Context, id string) error
	ListMigrationItems(ctx context.Context, sessionID string) ([]store.MigrationItem, error)
	SetMigrationItemPlan(ctx context.Context, sessionID, sourceDB, targetName, targetVersion string, selected bool) error
	MarkMigrationItemTargetCreated(ctx context.Context, sessionID, sourceDB, version string) error
	SetMigrationItemStatus(ctx context.Context, sessionID, sourceDB, status, reason string, checked, mismatched int, detail string, now time.Time) error
}

MigrationHubStore is the store surface the migration hub routes need.

type ModelPreflightService ¶ added in v0.2.0

type ModelPreflightService interface {
	PreflightEnabled() bool
	Preflight(ctx context.Context, in models.PreflightInput) (models.PreflightResult, error)
	CacheEnabled() bool
	CacheList(ctx context.Context) (models.CacheReport, error)
	CachePrune(ctx context.Context, volumes []string, dryRun bool) (models.CachePruneResult, error)
}

ModelPreflightService is the optional surface behind the Hugging Face preflight and model cache routes. The models service implements it.

type ModelService ¶ added in v0.2.0

type ModelService interface {
	Create(ctx context.Context, in models.CreateInput) (models.Created, error)
	List(ctx context.Context) ([]models.View, error)
	Get(ctx context.Context, name string) (models.View, error)
	Delete(ctx context.Context, name string) error
	Restart(ctx context.Context, name string) error
	RotateKey(ctx context.Context, name string) (string, error)
	ListKeys(ctx context.Context, model string) ([]models.KeyView, error)
	CreateKey(ctx context.Context, model string, in models.CreateKeyInput) (models.CreatedKey, error)
	RevokeKey(ctx context.Context, model, id string) error
	RotateKeyByID(ctx context.Context, model, id, actor string, grace *time.Duration) (models.CreatedKey, error)
	Usage(ctx context.Context, model string, window time.Duration) (models.UsageReport, error)
	EngineMetrics(ctx context.Context, name string, window time.Duration) (models.EngineMetricsReport, error)
	FitCheck(ctx context.Context, req models.FitRequest) (models.FitReport, error)
	SetResidency(ctx context.Context, name, residency string, idleTTL time.Duration) error
	SetSwapGroup(ctx context.Context, name, group string) error
	Wake(ctx context.Context, name string) error
	Sleep(ctx context.Context, name string) error
	ModelFit(ctx context.Context, name string) (models.FitReport, error)
	SetHFToken(ctx context.Context, name, token string) error
	GPUNodes(ctx context.Context) ([]models.GPUNode, error)
	NodeGPU(ctx context.Context, nodeID string) (gpu.Info, bool, error)
	UnplacedWorkloads(ctx context.Context) ([]models.Unplaced, error)
	SetLocalNodeID(id string)
}

ModelService is the surface the /api/v1/models handlers need from internal/models.

type NetworkShareSecretsSetter ¶ added in v0.2.0

type NetworkShareSecretsSetter interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
}

NetworkShareSecretsSetter is the surface network share create/update needs from internal/secrets.Manager, the same two-method shape RegistryCredentialSecretsSetter establishes: SetValue writes a CIFS share's password, never read back by any handler in this file.

type NetworkShareStore ¶ added in v0.2.0

type NetworkShareStore interface {
	SaveNetworkShare(ctx context.Context, s store.NetworkShare) error
	GetNetworkShare(ctx context.Context, id string) (store.NetworkShare, error)
	GetNetworkShareByName(ctx context.Context, name string) (store.NetworkShare, error)
	ListNetworkShares(ctx context.Context) ([]store.NetworkShare, error)
	UpdateNetworkShare(ctx context.Context, id, name, protocol, host, remotePath, mountOptions, username string) error
	DeleteNetworkShare(ctx context.Context, id string) error
}

NetworkShareStore is the store surface the network share handlers need.

type NodeMeshStatusSource ¶ added in v0.2.0

type NodeMeshStatusSource interface {
	MeshStatus(ctx context.Context, nodeID string) (network.Status, error)
}

NodeMeshStatusSource reads a remote node's own WireGuard view over its agent session.

type NodeProviderSecrets ¶ added in v0.2.0

type NodeProviderSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
}

NodeProviderSecrets is the surface the node provider credential handlers need from internal/secrets.Manager: unlike CloudflareDNSSecrets (write-only from this package's own perspective), this package does call Resolve, to authenticate the live provider API calls handleCreateNodeProvision and the catalog handlers below make. *secrets.Manager satisfies this structurally.

type NodeProvisionStore ¶ added in v0.2.0

type NodeProvisionStore interface {
	SaveNodeProvision(ctx context.Context, p store.NodeProvision) error
	GetNodeProvision(ctx context.Context, id string) (store.NodeProvision, error)
	ListNodeProvisions(ctx context.Context) ([]store.NodeProvision, error)
	UpdateNodeProvisionStatus(ctx context.Context, id, status, providerServerID, ipAddress, nodeID, failureReason string, updatedAt time.Time) error
}

NodeProvisionStore is the store surface the node provisioning handlers need.

type NodeProvisioner ¶ added in v0.2.0

type NodeProvisioner interface {
	ListRegions(ctx context.Context) ([]provision.Region, error)
	ListSizes(ctx context.Context, region string) ([]provision.Size, error)
	CreateServer(ctx context.Context, opts provision.CreateOpts) (serverID, ipAddr string, err error)
	GetServer(ctx context.Context, id string) (status provision.ServerStatus, ipAddr string, err error)
	DeleteServer(ctx context.Context, id string) error
}

NodeProvisioner is the surface internal/provision.Provisioner implementations satisfy: a narrow, consumer-defined interface so this package's own tests inject a fake instead of hitting a real cloud API.

type NodeProvisionerFactory ¶ added in v0.2.0

type NodeProvisionerFactory func(provider, token string) (NodeProvisioner, error)

NodeProvisionerFactory builds a NodeProvisioner for a known provider name given its resolved API token. Overridable in this package's own tests via WithNodeProvisionerFactory, the same "seam, not an interface" shape gitSourceFetch/fetch already use elsewhere in this package.

type NodeRuntimeResolver ¶

type NodeRuntimeResolver func(nodeID string) (docker.Runtime, error)

NodeRuntimeResolver picks the docker.Runtime that owns a given nodeID: the control plane's own local Docker daemon for "" (the store's established "empty NodeID means this control plane's own local node" convention, store.DesiredService.NodeID's own doc comment), or a connected remote agent's Transport for anything else. This is a func type, not an interface, the same pattern fetchFunc (builds.go) already establishes for a single-method seam: the real implementation is cmd/levelrail/main.go's own resolveNodeTransport (used identically by every reconciler controller to pick which node's Runtime it drives), passed in as a closure over that function's own registry rather than this package importing internal/agent.Registry directly.

handleExecApp (exec.go) is the first internal/api handler that needs live, per-request node routing: DockerPinger/ImageLister/ DockerDiskUsager/DockerPruner above are all fixed to this control plane's own local daemon, a deliberate choice their own doc comments explain (adding a method to docker.Runtime itself would ripple into every one of Runtime's 6+ fake implementations across internal/reconcile and internal/agent's test files, for the sake of one read-only or narrowly-scoped signal). Exec has the opposite requirement: it must reach whichever node the target app's container is actually running on, so it needs the resolver, not a fixed Runtime.

type NodeSessionCloser ¶ added in v0.2.0

type NodeSessionCloser interface {
	Disconnect(nodeID string)
}

NodeSessionCloser ends a node's live agent session. *agent.Server satisfies it; nil means a revoked node is only refused at its next connection.

type NodeStore ¶

type NodeStore interface {
	ListNodes(ctx context.Context) ([]store.Node, error)
	GetNode(ctx context.Context, id string) (*store.Node, error)
	DeleteNode(ctx context.Context, id string) error
	SaveNodeJoinToken(ctx context.Context, t store.NodeJoinToken) error
	UpdateNodeWorkloads(ctx context.Context, id string, acceptsApp, acceptsBuild bool) error
	// UpdateNodeRegion sets a node's optional location label, backing
	// PUT /api/v1/nodes/{id}/region.
	UpdateNodeRegion(ctx context.Context, id, region string) error
	// SetNodeSchedulable is the cordon/uncordon mutation.
	SetNodeSchedulable(ctx context.Context, id string, schedulable bool) error
	RevokeNodeCert(ctx context.Context, id string, now time.Time) error
}

NodeStore is the store surface the node-management handlers need. *store.DB satisfies this structurally, the same narrow consumer-defined interface convention every other Store sub-interface in this package already follows.

type NotificationChannelTester ¶

type NotificationChannelTester interface {
	SendTest(ctx context.Context, kind alerting.NotifyKind, notifyURL string) error
}

NotificationChannelTester is the surface the test-send routes need: a real send through kind/notifyURL, not just a format check. *alerting.DeployDispatcher satisfies this structurally.

type NotificationChannels ¶

type NotificationChannels interface {
	SaveNotificationChannel(ctx context.Context, c alerting.NotificationChannel) error
	GetNotificationChannel(ctx context.Context, id string) (*alerting.NotificationChannel, error)
	ListNotificationChannels(ctx context.Context) ([]alerting.NotificationChannel, error)
	DeleteNotificationChannel(ctx context.Context, id string) error
}

NotificationChannels is the store surface the channel handlers need. *alerting.DB satisfies this structurally.

type NotificationDeliveryStore ¶

type NotificationDeliveryStore interface {
	RecordNotificationDelivery(ctx context.Context, d alerting.NotificationDelivery) error
	ListNotificationDeliveries(ctx context.Context, channelID string, limit int, before *time.Time) ([]alerting.NotificationDelivery, error)
}

NotificationDeliveryStore is the surface the delivery-history route and test-send recording need. *alerting.DB satisfies this structurally.

type OAuthIdentityStore ¶

type OAuthIdentityStore interface {
	GetOAuthIdentity(ctx context.Context, provider, providerUserID string) (*store.OAuthIdentity, error)
	SaveOAuthIdentity(ctx context.Context, i store.OAuthIdentity) error
	ListOAuthIdentitiesForUser(ctx context.Context, userID string) ([]store.OAuthIdentity, error)
}

OAuthIdentityStore is the store surface the sign-in and account- linking handlers need. *store.DB satisfies this structurally.

type OAuthSecrets ¶

type OAuthSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
}

OAuthSecrets is the surface the OAuth flow needs from internal/secrets.Manager: unlike SecretSetter (write-only), a provider's client secret must be resolved on every sign-in attempt.

type OAuthSettingsStore ¶

type OAuthSettingsStore interface {
	GetOAuthProviderSettings(ctx context.Context, provider string) (store.OAuthProviderSettings, error)
	ListOAuthProviderSettings(ctx context.Context) ([]store.OAuthProviderSettings, error)
	UpdateOAuthProviderSettings(ctx context.Context, s store.OAuthProviderSettings) error
}

OAuthSettingsStore is the store surface the OAuth settings and sign-in handlers need for provider configuration (oauth_settings.go, this file). *store.DB satisfies this structurally.

type OIDCJWKSProvider ¶ added in v0.2.0

type OIDCJWKSProvider interface {
	JWKS(ctx context.Context) (oidc.JWKS, error)
}

OIDCJWKSProvider serves the public half of the signing key pipeline jobs' OIDC tokens are signed with. *oidc.Manager satisfies it.

type OIDCKeyRotator ¶ added in v0.2.0

type OIDCKeyRotator interface {
	RotateKey(ctx context.Context, retireAfter time.Duration) (oidc.RotationResult, error)
}

OIDCKeyRotator rotates the pipeline OIDC signing key. *oidc.Manager satisfies it. A separate interface from OIDCJWKSProvider, not a wider one: a fake in a JWKS-only test has no reason to implement rotation too.

type ObjectClientResolver ¶ added in v0.2.0

type ObjectClientResolver interface {
	Client(ctx context.Context, targetID string) (*objectstore.Client, store.BackupTarget, error)
}

ObjectClientResolver builds a bucket client for a stored destination.

type ObservabilityStore ¶ added in v0.2.0

type ObservabilityStore interface {
	GetObservabilitySettings(ctx context.Context) (store.ObservabilitySettings, error)
	UpdateObservabilitySettings(ctx context.Context, s store.ObservabilitySettings) error
}

ObservabilityStore is the store surface GET/PUT /api/v1/settings/observability need: the single platform-wide row, always present, the same shape EmailSettingsStore has for its own row.

type OnboardingStore ¶

type OnboardingStore interface {
	GetOnboardingState(ctx context.Context) (store.OnboardingState, error)
	MarkOnboardingCompleted(ctx context.Context) error
	SetOnboardingProgress(ctx context.Context, currentStep string, steps map[string]string) error
}

OnboardingStore is the store surface the /api/v1/onboarding routes need.

type Option ¶

type Option func(*Router)

Option configures optional Router behavior.

func WithACMEDefaultDirectory ¶ added in v0.2.0

func WithACMEDefaultDirectory(url string) Option

WithACMEDefaultDirectory sets the CA directory enable-https uses when the caller does not ask for staging. Empty means Let's Encrypt production.

func WithAIAssistantSecrets ¶ added in v0.2.0

func WithAIAssistantSecrets(s AIAssistantSecrets) Option

WithAIAssistantSecrets enables PUT/DELETE /api/v1/settings/ai-assistant. Without one configured (the default), both return 501; GET works regardless, the same shape WithEmailSecrets establishes.

func WithAIEngine ¶ added in v0.2.0

func WithAIEngine(e AIEngine) Option

WithAIEngine enables every /api/v1/ai/... chat session route. Without one configured (the default), they all return 501, the same "not configured" shape WithBuilder's absence produces for the build trigger route.

func WithAPIRateLimit ¶

func WithAPIRateLimit(readPerMinute, writePerMinute int) Option

WithAPIRateLimit enables a general per-actor rate limit across every requireAbility-gated route (api_rate_limit.go): read-tier (AbilityRead) requests get readPerMinute budget, everything else (write, write:sensitive, deploy, root) gets the stricter writePerMinute budget, keyed per bearer token, per session user, or per client IP for an unauthenticated caller. Either argument <= 0 disables that tier. Without this option (the default), the whole check is skipped: existing tests and any embedder that never opts in see unthrottled behavior, the same "nil is valid" shape WithSecretSetter's own absence already establishes. This package never reads the environment directly: cmd/levelrail/main.go reads APP_API_RATE_LIMIT_READ_RPM / APP_API_RATE_LIMIT_WRITE_RPM and calls this unconditionally with their resolved (env-or-default) values, so the real control plane is always protected even when an operator never sets either env var.

func WithAgentCAFingerprint ¶ added in v0.2.0

func WithAgentCAFingerprint(fp string) Option

WithAgentCAFingerprint sets the agent CA fingerprint returned with each new node join token, so the enrollment command can pin it.

func WithAlertNoise ¶ added in v0.2.0

func WithAlertNoise(a AlertNoise) Option

WithAlertNoise enables the silence, maintenance window and alert history routes.

func WithAlertRules ¶

func WithAlertRules(a AlertRules) Option

WithAlertRules enables POST/GET /api/v1/apps/{name}/alerts and DELETE /api/v1/apps/{name}/alerts/{id}. Without one configured (the default), all three routes return 501, the same "not configured" shape WithSecretSetter and WithTelemetryQuerier's absence already produce: this control plane still starts and serves everything else without an alerting.DB wired in.

func WithAllowInsecureLogin ¶ added in v0.2.0

func WithAllowInsecureLogin(allow bool) Option

WithAllowInsecureLogin permits plain-HTTP sign-in even when an https dashboard URL is configured.

func WithAppTeardownOptions ¶ added in v0.2.0

func WithAppTeardownOptions(opts ...application.Option) Option

WithAppTeardownOptions sets the application options (instance ID, network prefix) used to match containers when an app is deleted, so delete never touches containers another control plane created on a shared daemon.

func WithApprovalChatNotifier ¶ added in v0.2.0

func WithApprovalChatNotifier(n ApprovalChatNotifier) Option

WithApprovalChatNotifier enables posting an interactive Approve/Deny message (deploy_approval_chat_notify.go) when a deploy approval is requested, to every notification channel that opted into InteractiveApprovals. Without one, requestDeployApproval simply doesn't post anything, the same "optional signal" shape as WithDeployNotifier.

func WithAuditLogRetention ¶

func WithAuditLogRetention(d time.Duration) Option

WithAuditLogRetention overrides how long an audit_log row survives before PurgeOldAuditEntries removes it. Without one configured (or passed as 0), defaultAuditLogRetention (90 days) applies. Same "no hardcoded thresholds, use env vars" shape as WithPreviewTTL: this package never reads the environment directly, cmd/levelrail/main.go reads APP_AUDIT_LOG_RETENTION_DAYS and passes the parsed duration here.

func WithAuthEngine ¶ added in v0.2.0

func WithAuthEngine(e *authengine.Engine) Option

WithAuthEngine supplies the library auth engine. NewRouter builds a default one over the store database when none is given.

func WithAuthEngineMFA ¶ added in v0.2.0

func WithAuthEngineMFA(m *authengine.MFA) Option

WithAuthEngineMFA serves TOTP and passkeys through the library on the existing routes. A nil service is a no-op, which keeps the built-in handlers.

func WithAutoPlacement ¶ added in v0.2.0

func WithAutoPlacement(enabled bool) Option

WithAutoPlacement overrides whether handleCreateApp/handleCreateDatabase auto-place a create request that omits node_id onto the least-loaded registered node (scheduling.go's autoPlaceNode), instead of leaving it on this control plane's own local node. Defaults to true (NewRouter's own struct literal); cmd/levelrail/main.go reads APP_AUTO_PLACEMENT and calls this with false to disable it, the same "this package never reads the environment directly" convention WithSessionTTL's own doc comment establishes. An explicit node_id in a create request always overrides this regardless of its value, on or off.

func WithBackupDeleter ¶ added in v0.2.0

func WithBackupDeleter(d BackupDeleter) Option

WithBackupDeleter enables DELETE /api/v1/databases/{name}/backups/{historyId} and its app volume counterpart. Without one configured (the default), both routes return 501, the same "not configured" shape WithBackupDownloader's absence produces: deleting the stored object behind a backup needs the same live secretsManager to resolve a target's credentials that downloading or uploading one does.

func WithBackupDownloader ¶

func WithBackupDownloader(d BackupDownloader) Option

WithBackupDownloader enables GET /api/v1/databases/{name}/backups/{historyId}/download. Without one configured (the default), that route returns 501, the same "not configured" shape WithBackupRunner's absence produces: both need a live secretsManager to resolve a target's credentials, WithBackupRunner to upload with them, this to download with them.

func WithBackupProtection ¶ added in v0.2.0

func WithBackupProtection(p BackupProtection, st BackupProtectionStore) Option

WithBackupProtection enables the backup health, restore drill, volume backup policy and restore-to routes. Without it they return 501.

func WithBackupRunner ¶

func WithBackupRunner(r BackupRunner) Option

WithBackupRunner enables POST /api/v1/databases/{name}/backups. Without one configured (the default), that route returns 501, the same "not configured" shape WithBackupSecrets' absence produces; listing backup history (GET .../backups) works regardless, since it only reads store.BackupHistory rows a runner already wrote in the past, nothing about it needs a live runner today.

func WithBackupSecrets ¶

func WithBackupSecrets(s BackupSecretsSetter) Option

WithBackupSecrets enables POST /api/v1/backup-targets. Without one configured (the default), that route returns 501, the same "not configured" shape WithSecretSetter's absence produces; listing, getting, and deleting an already-connected backup target work regardless, since none of those need to write a credential.

func WithBackupTargetTester ¶

func WithBackupTargetTester(t BackupTargetTester) Option

WithBackupTargetTester enables POST /api/v1/backup-targets/{id}/test. Without one configured (the default), that route returns 501, the same "not configured" shape WithRegistryAuthTester's absence produces for registry credentials: both need a live secretsManager to resolve a stored credential before using it.

func WithBackupVerifier ¶

func WithBackupVerifier(v BackupVerifier) Option

WithBackupVerifier enables POST /api/v1/databases/{name}/backups/{historyId}/verify. Without one configured (the default), that route returns 501, the same "not configured" shape WithBackupRunner's absence produces: verifying needs the identical live secretsManager WithBackupRunner and WithBackupDownloader already depend on, to resolve a target's credentials and re-download the object. Listing past verification attempts (GET .../verifications) works regardless, the same "listing needs no live runner" reasoning WithBackupRunner's own doc comment gives for backup history.

func WithBaseBackupRunner ¶ added in v0.2.0

func WithBaseBackupRunner(r BaseBackupRunner) Option

WithBaseBackupRunner enables POST /api/v1/databases/{name}/base-backups. Without one configured (the default), that route returns 501, the same "not configured" shape WithBackupRunner's absence produces; listing base backup history (GET .../base-backups) works regardless, the same "listing needs no live runner" reasoning WithBackupRunner's own doc comment gives.

func WithBitbucketAppSecrets ¶

func WithBitbucketAppSecrets(s BitbucketAppSecrets) Option

WithBitbucketAppSecrets enables the Bitbucket App routes that read or write a credential (connect, connect-start, callback, repo/branch listing, use-as-source). Without one configured, those return 501; GET/DELETE /api/v1/bitbucket-app work regardless.

func WithBuilder ¶

func WithBuilder(b Builder) Option

WithBuilder enables POST /api/v1/apps/{name}/builds: a manual build trigger for an operator with no working git webhook configured (see internal/webhook.Config's own doc comment on why that path is deliberately static, single-app configuration). Without one configured (the default), that route returns 501, the same "not configured" shape WithSecretSetter/WithTelemetryQuerier/WithAlertRules absence already share. cmd/levelrail/main.go passes the same *deploy.Pipeline the git webhook receiver uses, when a BuildKit connection was successfully established at startup.

func WithCapacityForecastLookback ¶ added in v0.2.0

func WithCapacityForecastLookback(d time.Duration) Option

WithCapacityForecastLookback overrides how far back GET /api/v1/nodes/{id}/capacity-forecast looks for disk/memory history (handleNodeCapacityForecast). Without one configured (or passed as 0), defaultCapacityForecastLookback (14 days) applies. Same "no hardcoded thresholds, use env vars" shape as WithResourceRecommendationLookback: this package never reads the environment directly, cmd/levelrail/main.go reads APP_CAPACITY_FORECAST_LOOKBACK and passes the parsed duration here.

func WithCertExpiryWarningWindow ¶

func WithCertExpiryWarningWindow(d time.Duration) Option

WithCertExpiryWarningWindow overrides how far ahead of a certificate's expiry GET /api/v1/certificates starts reporting "expiring_soon" instead of "healthy" (see alerting.CertExpiryStatus). Without one configured (or passed as 0), alerting.DefaultCertExpiryWarningWindow (14 days) applies. Same "no hardcoded thresholds, use env vars" shape as WithSessionTTL: this package never reads the environment directly, cmd/levelrail/main.go reads APP_CERT_EXPIRY_WARNING_WINDOW and passes the parsed duration here, and to alerting.NewEngine for a kind=cert_expiry rule's own evaluation.

func WithCertRenewalTracking ¶ added in v0.2.0

func WithCertRenewalTracking(src CertObservationSource, stalledThreshold time.Duration) Option

WithCertRenewalTracking lets GET /api/v1/certificates report a stalled renewal from the observations kind=cert_expiry rules record. A zero threshold falls back to alerting.DefaultCertRenewalStalledThreshold.

func WithChangelog ¶ added in v0.2.0

func WithChangelog(entries []changelog.Entry) Option

WithChangelog supplies the entries GET /api/v1/changelog serves, parsed once at startup from the repo's own CHANGELOG.md (see cmd/levelrail's loadChangelog). Without one configured (the default, nil), that route answers an empty entries list rather than failing: a bare, non-Docker install that hasn't shipped CHANGELOG.md next to the binary yet just sees an empty "what's new" panel, not a broken one.

func WithCloneRestoreRunner ¶

func WithCloneRestoreRunner(r CloneRestoreRunner) Option

WithCloneRestoreRunner enables POST /api/v1/databases/{name}/restore-as-new. Without one configured (the default), that route returns 501, the same "not configured" shape WithRestoreRunner's absence produces; listing clone-restore history (GET .../clone-restores) works regardless, the same "listing needs no live runner" reasoning WithRestoreRunner's own doc comment gives.

func WithCloudflareDNSSecrets ¶

func WithCloudflareDNSSecrets(s CloudflareDNSSecrets) Option

WithCloudflareDNSSecrets enables PUT/DELETE /api/v1/settings/cloudflare-dns. Without one configured (the default), both return 501; GET works regardless, the same shape WithCloudflareTunnelSecrets establishes.

func WithCloudflareDNSTokenResolver ¶ added in v0.2.0

func WithCloudflareDNSTokenResolver(r CloudflareDNSTokenResolver) Option

WithCloudflareDNSTokenResolver enables GET/POST/PUT/DELETE .../dns-records whenever Cloudflare DNS is the configured provider: without one set (the default), those routes return 501 unless Route53 DNS is configured instead. Distinct from WithCloudflareDNSSecrets, which can only check whether a token is stored, not read it back.

func WithCloudflareTunnelSecrets ¶

func WithCloudflareTunnelSecrets(s CloudflareTunnelSecrets) Option

WithCloudflareTunnelSecrets enables PUT/DELETE /api/v1/settings/cloudflare-tunnel. Without one configured (the default), both return 501; GET works regardless, the same shape WithEmailSecrets establishes.

func WithComposeSecrets ¶

func WithComposeSecrets(s ComposeSecretStore) Option

WithComposeSecrets enables POST /api/v1/apps/{name}/compose to resolve a compose file's generatable SERVICE_ magic vars. Without one configured (the default), that endpoint still works for compose files with no such vars, and fails with a clear error for ones that need one.

func WithContainerLister ¶

func WithContainerLister(l ContainerLister) Option

WithContainerLister enables GET /api/v1/system/containers. Without one configured (the default), that route returns 501, the same "not configured" shape WithExecRuntime's own absence already has.

func WithControlPlaneBackupScheduleDisabled ¶ added in v0.2.0

func WithControlPlaneBackupScheduleDisabled(disabled bool) Option

WithControlPlaneBackupScheduleDisabled tells the doctor check that scheduled snapshots are switched off, so it reports unknown, not a warning.

func WithControlPlaneBackups ¶ added in v0.2.0

func WithControlPlaneBackups(m ControlPlaneBackupManager) Option

WithControlPlaneBackups enables the /api/v1/system/backups routes. Without one, they return 501.

func WithControlPlaneDR ¶ added in v0.2.0

func WithControlPlaneDR(dr ControlPlaneDR, material EscrowMaterialReader) Option

WithControlPlaneDR enables the /api/v1/system/control-plane-dr routes and the doctor's disaster recovery check. Without it the routes return 501.

func WithDBPinger ¶

func WithDBPinger(p DBPinger) Option

WithDBPinger enables the database check on GET /api/v1/system/doctor. Without one configured (the default), that check reports unknown, the same "optional signal, absence is not an error" shape WithDockerPinger's own absence already has.

func WithDashboardCache ¶ added in v0.2.0

func WithDashboardCache(ttl, stale time.Duration) Option

WithDashboardCache enables the short TTL cache behind the dashboard's aggregate endpoints and the docker disk usage warm-up. ttl <= 0 keeps every request live; the binary resolves APP_DASHBOARD_CACHE_TTL and _STALE.

func WithDashboardListenAddr ¶ added in v0.2.0

func WithDashboardListenAddr(addr string) Option

WithDashboardListenAddr tells the reverse proxy guide where the dashboard listens.

func WithDashboardURL ¶ added in v0.2.0

func WithDashboardURL(u string) Option

WithDashboardURL sets the base URL used for links in outbound notices.

func WithDataDir ¶

func WithDataDir(path string) Option

WithDataDir enables disk-usage reporting on GET /api/v1/system/status. path should be the same APP_DATA_DIR the control plane itself was started with. Without one configured (the default), the status response simply omits the two data-dir byte fields, the same "optional signal, absence is not an error" shape WithSecretSetter's own absence already has for secret-setting.

func WithDatabaseAccess ¶ added in v0.2.0

func WithDatabaseAccess(s DatabaseAccessStore, ttl dbaccess.TTLLimits) Option

WithDatabaseAccess enables database users, temporary credentials and the network controls. ttl bounds temporary credential lifetimes.

func WithDatabaseUpgrader ¶ added in v0.2.0

func WithDatabaseUpgrader(u DatabaseUpgrader) Option

WithDatabaseUpgrader enables the database upgrade routes; without it they return 501.

func WithDeleteTeardownTimeout ¶ added in v0.2.0

func WithDeleteTeardownTimeout(d time.Duration) Option

WithDeleteTeardownTimeout bounds how long DELETE /apps/{name} waits for containers to stop.

func WithDeployApprovalTTL ¶ added in v0.2.0

func WithDeployApprovalTTL(d time.Duration) Option

WithDeployApprovalTTL overrides how long a pending deploy approval (deploy_approvals.go) stays decidable before it's treated as expired. Without one configured (or passed as 0), defaultDeployApprovalTTL (24 hours) applies. Same "no hardcoded thresholds, use env vars" shape as WithAuditLogRetention: this package never reads the environment directly, cmd/levelrail/main.go reads APP_DEPLOY_APPROVAL_TTL and passes the parsed duration here.

func WithDeployGate ¶ added in v0.2.0

func WithDeployGate(s DeployGateStore, defaults deploysafety.Effective) Option

WithDeployGate enables the gate timeline, deploy safety settings and rollback target endpoints, resolving settings over defaults.

func WithDeployLogQuerier ¶

func WithDeployLogQuerier(s DeployLogQuerier) Option

WithDeployLogQuerier enables a finished deploy attempt's full log replay on GET /api/v1/apps/{name}/deploys/{deployId}/logs. Without one configured (the default), that route returns 501 for an attempt that has already finished; an in-progress attempt's live tail is unaffected (see WithDeployRecorder). cmd/levelrail/main.go passes the same *telemetry.DB internal/deploylog.Recorder writes to.

func WithDeployMaxConcurrent ¶ added in v0.2.0

func WithDeployMaxConcurrent(n int) Option

WithDeployMaxConcurrent caps how many deploys run at once across all apps; deploys over the cap wait as queued. Zero or less means unlimited.

func WithDeployNotifier ¶

func WithDeployNotifier(n DeployNotifier) Option

WithDeployNotifier enables the actual send: without one configured (the default), a deploy attempt still finishes and is still recorded exactly as it is today, it just never dispatches a notification, even if WithDeployNotifyTargets has targets configured. Kept as a separate option from WithDeployNotifyTargets on purpose, the same "listing doesn't need a live runner" split WithBackupRunner/WithRestoreRunner already establish: a control plane can serve deploy-notify-target CRUD without necessarily having a dispatcher wired (e.g. in a test), and vice versa cmd/levelrail/main.go always wires both together from the same *alerting.DeployDispatcher, which itself embeds the *alerting.DB that WithDeployNotifyTargets is given.

func WithDeployNotifyTargets ¶

func WithDeployNotifyTargets(t DeployNotifyTargets) Option

WithDeployNotifyTargets enables POST/GET /api/v1/apps/{name}/deploy-notify-targets and DELETE .../deploy-notify-targets/{id}: CRUD for deploy-outcome notification destinations, the sibling surface to WithAlertRules for wave-2 deploy-outcome notifications. Without one configured (the default), all three routes return 501, the same "not configured" shape WithAlertRules' own absence produces.

func WithDeployRecorder ¶

func WithDeployRecorder(r *deploylog.Recorder) Option

WithDeployRecorder enables live build-log fan-out for the manual build trigger (handleTriggerBuild) and the in-progress side of GET /api/v1/apps/{name}/deploys/{deployId}/logs. Without one configured (the default), handleTriggerBuild falls back to build.SlogProgress (no persisted log, matching this package's pre-existing behavior) and the SSE log route returns 501 for any attempt still in progress. r must be the exact same *deploylog.Recorder instance passed to internal/webhook.New when a webhook handler is also wired: see that package's own doc comment for why a shared instance, not two independent ones, is required for a webhook-triggered attempt's live log to be visible through this router's SSE route.

func WithDeploySafety ¶ added in v0.2.0

func WithDeploySafety(s DeploySafetyStore, resolver docker.ImageResolver) Option

WithDeploySafety enables freeze windows, the stale-deploy guard and digest resolution for image deploys. resolver may be nil.

func WithDeviceLoginNotifier ¶ added in v0.2.0

func WithDeviceLoginNotifier(n DeviceLoginNotifier) Option

WithDeviceLoginNotifier enables the opt-in notice sent through channels with NotifyDeviceLogin when a CLI device login is waiting.

func WithDockerDiskUsager ¶

func WithDockerDiskUsager(u DockerDiskUsager) Option

WithDockerDiskUsager enables the docker_disk_usage field on GET /api/v1/system/status. Without one configured (the default), that field is simply omitted, the same "optional signal, absence is not an error" shape WithDockerPinger's own absence already has.

func WithDockerGuard ¶ added in v0.2.0

func WithDockerGuard(c DockerGuardController) Option

WithDockerGuard enables GET/PUT /api/v1/system/docker-guard, its doctor check and its attention item. Without it those report the guard as unknown.

func WithDockerPinger ¶

func WithDockerPinger(p DockerPinger) Option

WithDockerPinger enables Docker daemon connectivity reporting on GET /api/v1/system/status. Without one configured (the default), the response's DockerConnected field simply stays false, the same "optional signal, absence is not an error" shape WithDataDir and WithSecretSetter's own absence already have.

func WithDockerPruner ¶

func WithDockerPruner(p DockerPruner) Option

WithDockerPruner enables POST /api/v1/system/prune. Without one configured (the default), that route returns 501, the same "not configured" shape WithBuilder's own absence produces.

func WithDoctorAgentAdvertise ¶ added in v0.2.0

func WithDoctorAgentAdvertise(host string, port int) Option

WithDoctorAgentAdvertise tells the agent_advertise_reachability check which address a real agent would dial: APP_AGENT_ADVERTISE_HOST and the port cmd/levelrail/main.go's own agentAddr() binds the agent gRPC service on. Empty host or zero port makes that check report unknown.

func WithDoctorClockSkewWarnAge ¶ added in v0.2.0

func WithDoctorClockSkewWarnAge(d time.Duration) Option

WithDoctorClockSkewWarnAge overrides how far this host's clock may drift from the remote HTTP Date header the clock_skew check compares against before it warns. Zero keeps defaultDoctorClockSkewWarnAge.

func WithDoctorDiskIOWarnLatency ¶ added in v0.2.0

func WithDoctorDiskIOWarnLatency(d time.Duration) Option

WithDoctorDiskIOWarnLatency overrides the write+fsync latency the disk_io_latency check warns above, in place of defaultDoctorDiskIOWarnLatency. Zero keeps that default.

func WithDoctorDiskWarningBytes ¶

func WithDoctorDiskWarningBytes(n int64) Option

WithDoctorDiskWarningBytes overrides the free-space floor GET /api/v1/system/doctor's disk_space check warns below. Without one configured (or passed as 0), defaultDoctorDiskWarningBytes (1GiB) applies. Same "no hardcoded thresholds, use env vars" shape as WithCertExpiryWarningWindow: this package never reads the environment directly, cmd/levelrail/main.go reads APP_DOCTOR_DISK_WARNING_BYTES and passes the parsed value here.

func WithDoctorIngressPorts ¶ added in v0.2.0

func WithDoctorIngressPorts(httpPort, httpsPort int) Option

WithDoctorIngressPorts overrides the two ports GET /api/v1/system/ doctor's port_<n> checks probe, in place of the literal 80/443 defaultDoctorHTTPPort/defaultDoctorHTTPSPort ports. cmd/levelrail/ main.go passes the same APP_INGRESS_HTTP_ADDR/APP_INGRESS_HTTPS_ADDR- derived ports already threaded to ingressreconcile.WithHTTPListenAddr/ WithListenAddr, so doctor stays accurate for an instance running its ingress on non-default ports. Either argument being 0 keeps that port's own default.

func WithDoctorMasterKeyRotationWarnAge ¶

func WithDoctorMasterKeyRotationWarnAge(d time.Duration) Option

WithDoctorMasterKeyRotationWarnAge overrides the age GET /api/v1/system/doctor's master_key_rotation check warns beyond, the same env-var-overridable-threshold shape WithDoctorDiskWarningBytes already establishes for disk_space.

func WithDoctorMinCPUCount ¶ added in v0.2.0

func WithDoctorMinCPUCount(n int) Option

WithDoctorMinCPUCount overrides the recommended-minimum CPU core count the cpu check warns below. Zero keeps defaultDoctorMinCPUCount.

func WithDoctorMinRAMBytes ¶ added in v0.2.0

func WithDoctorMinRAMBytes(n int64) Option

WithDoctorMinRAMBytes overrides the recommended-minimum total memory the ram check warns below. Zero keeps defaultDoctorMinRAMBytes.

func WithDoctorNetworkTimeout ¶ added in v0.2.0

func WithDoctorNetworkTimeout(d time.Duration) Option

WithDoctorNetworkTimeout overrides the bounded overall timeout GET /api/v1/system/doctor's network checks (public_ip, external_reachability_*, acme_reachability, clock_skew) share, the same "no hardcoded thresholds" shape as WithDoctorDiskWarningBytes. Zero keeps defaultDoctorNetworkTimeout.

func WithDoctorPublicIPEndpoint ¶ added in v0.2.0

func WithDoctorPublicIPEndpoint(url string) Option

WithDoctorPublicIPEndpoint overrides the plain-text-IP endpoint the public_ip check queries, in place of defaultDoctorPublicIPEndpoint. Lets an operator behind an egress proxy, or one who simply distrusts a third-party default, point this at their own endpoint.

func WithDomainBasicAuthSecrets ¶

func WithDomainBasicAuthSecrets(s DomainBasicAuthSecrets) Option

WithDomainBasicAuthSecrets enables PUT/DELETE /api/v1/apps/{name}/domains/{domain}/auth. Without one configured (the default), both return 501; GET works regardless, the same shape WithCloudflareTunnelSecrets establishes.

func WithDomainTLSCertSecrets ¶

func WithDomainTLSCertSecrets(s DomainTLSCertSecrets) Option

WithDomainTLSCertSecrets enables PUT/DELETE /api/v1/apps/{name}/domains/{domain}/tls-cert. Without one configured (the default), both return 501; GET works regardless, the same shape WithDomainBasicAuthSecrets establishes.

func WithEmailSecrets ¶

func WithEmailSecrets(s EmailSecretsStore) Option

WithEmailSecrets enables PUT /api/v1/settings/email. Without one configured (the default), that route returns 501; GET works regardless.

func WithEmailSender ¶

func WithEmailSender(s email.Sender) Option

WithEmailSender enables the actual send behind POST /api/v1/auth/forgot-password. Without one, that route still returns its generic success response, it just never sends anything.

func WithExecRuntime ¶

func WithExecRuntime(r NodeRuntimeResolver) Option

WithExecRuntime enables POST /apps/{name}/exec (exec.go's handleExecApp). Without one configured (the default), that route returns 501, the same "not configured" shape WithDockerPruner's absence produces: a control plane can run every other route with no resolver wired in, exec is the one action that needs live node routing (NodeRuntimeResolver's own doc comment).

func WithExposure ¶ added in v0.2.0

func WithExposure(m *exposure.Manager, s ExposureStore) Option

WithExposure enables the exposure audit and its guided restrictions. Without it, the exposure routes return 501 and the doctor group is absent.

func WithFirewallRequiredPorts ¶ added in v0.2.0

func WithFirewallRequiredPorts(ports []int) Option

WithFirewallRequiredPorts overrides the ports POST /api/v1/firewall-rules refuses to deny or CIDR-restrict, defaulting to firewall.DefaultRequiredPorts. Pass this instance's actually configured management API, agent gRPC, and ingress ports whenever any of them were moved off their default address: a lockout check against the wrong port number is no check at all.

func WithGPUHostDiagnoser ¶ added in v0.2.0

func WithGPUHostDiagnoser(d GPUHostDiagnoser) Option

WithGPUHostDiagnoser adds the detailed host GPU checks to the doctor report. Without it the doctor shows only what nodes report.

func WithGitHubAppManifestConfig ¶

func WithGitHubAppManifestConfig(cfg githubapp.ManifestConfig) Option

WithGitHubAppManifestConfig overrides the permissions/events a fresh App registration requests (githubapp.BuildManifest's own doc comment). Without one configured, NewRouter defaults to githubapp.DefaultManifestConfig(), the same request this codebase has always sent.

func WithGitHubAppSecrets ¶

func WithGitHubAppSecrets(s GitHubAppSecrets) Option

WithGitHubAppSecrets enables the GitHub App routes that read or write a credential (manifest callback, installation callback, repo/branch listing). Without one configured, those return 501; GET/DELETE /api/v1/github-app work regardless.

func WithGitLabAppSecrets ¶

func WithGitLabAppSecrets(s GitLabAppSecrets) Option

WithGitLabAppSecrets enables the GitLab App routes that read or write a credential (connect, connect-start, callback, project listing, use-as-source). Without one configured, those return 501; GET/DELETE /api/v1/gitlab-app work regardless.

func WithGitSourceSecrets ¶

func WithGitSourceSecrets(s GitSourceSecrets) Option

WithGitSourceSecrets enables PUT /api/v1/apps/{name}/git-source and the git-push webhook route. Without one configured (the default), both return 501; listing/getting/deleting a git source works regardless, since neither needs to resolve a credential.

func WithGiteaAppSecrets ¶ added in v0.2.0

func WithGiteaAppSecrets(s GiteaAppSecrets) Option

WithGiteaAppSecrets enables the Gitea App routes that read or write a credential (connect, connect-start, callback, repo/branch listing, use-as-source). Without one configured, those return 501; GET/DELETE /api/v1/gitea-app work regardless.

func WithHSTS ¶ added in v0.2.0

func WithHSTS(enabled bool) Option

WithHSTS enables the Strict-Transport-Security response header. Without it (the default), Strict-Transport-Security is never sent, see Router.hstsEnabled's own doc comment for why that's the safe default.

func WithHTTPSEnableLimits ¶ added in v0.2.0

func WithHTTPSEnableLimits(maxPerHour int, pendingTimeout time.Duration) Option

WithHTTPSEnableLimits sets how many production enable attempts are allowed per hour (a guard against burning Let's Encrypt's failed-validation limit) and how long a certificate may stay pending before it is reported failed. Zero values keep the defaults.

func WithHostFirewallRunner ¶ added in v0.2.0

func WithHostFirewallRunner(run func(ctx context.Context, name string, args ...string) ([]byte, error), lookPath func(string) (string, error)) Option

WithHostFirewallRunner replaces how the host firewall endpoints run and find ufw, so tests never touch the real firewall.

func WithImageLister ¶

func WithImageLister(l ImageLister) Option

WithImageLister enables GET /api/v1/apps/{name}/images. Without one configured (the default), that route returns an empty list rather than 501 or 404: the deploy trigger form's manual text input always works regardless, so a missing image lister is "no suggestions available" (an empty array), not a request error, the same "optional signal, absence is not an error" shape WithDockerPinger's own absence already has.

func WithIngressPortOwner ¶ added in v0.2.0

func WithIngressPortOwner(o IngressPortOwner) Option

WithIngressPortOwner lets GET /api/v1/system/doctor's port_<n> checks recognize a bind failure caused by this control plane's own embedded ingress (internal/ingress.Driver) as expected rather than a real problem. Without one configured (the default), a bound port always reports fail, the same behavior this check had before ingress ownership was distinguishable.

func WithInviteTTL ¶

func WithInviteTTL(d time.Duration) Option

WithInviteTTL overrides how long a team invite stays acceptable before GetInviteByHash's expiry check rejects it. Without one configured (or passed as 0), defaultInviteTTL (7 days) applies. Same "no hardcoded thresholds, use env vars" shape as WithPreviewTTL: this package never reads the environment directly, cmd/levelrail/main.go reads APP_INVITE_TTL and passes the parsed duration here.

func WithJobLimits ¶ added in v0.2.0

func WithJobLimits(maxPerApp int) Option

WithJobLimits sets the per-app job count cap (APP_JOBS_MAX_PER_APP).

func WithLoadBalancers ¶ added in v0.2.0

func WithLoadBalancers(s LoadBalancerStore, registry *loadbalancer.Registry, stats loadbalancer.StatsSource) Option

WithLoadBalancers enables the /api/v1/apps/{name}/loadbalancer routes. registry and stats feed the live status route and may be nil.

func WithLogBroadcaster ¶

func WithLogBroadcaster(b *telemetry.LogBroadcaster) Option

WithLogBroadcaster enables GET /api/v1/apps/{name}/logs/stream, the live-tailing counterpart to WithTelemetryQuerier's historical GET .../logs. Without one configured (the default), that route returns 501, the same "not configured" shape WithDeployRecorder's absence produces for the in-progress side of a deploy attempt's log. Deliberately a separate option from WithTelemetryQuerier even though both are telemetry-flavored and cmd/levelrail/main.go always configures them together in practice: a control plane could in principle want historical log search without live tailing (or the reverse), and keeping them as two options leaves that possible instead of one silently implying the other. b must be the exact same *telemetry.LogBroadcaster instance passed to telemetry.NewLogCollector: see that constructor's own doc comment for why a shared instance, not two independent ones, is required for a line StreamOne receives from Docker to ever reach a viewer connected through this router's SSE route.

func WithLoginCodeKey ¶ added in v0.2.0

func WithLoginCodeKey(key []byte) Option

WithLoginCodeKey peppers stored sign-in code hashes with a key derived from the control plane's own secret, so a database copy alone cannot brute-force a live code.

func WithMajorUpgrader ¶ added in v0.2.0

func WithMajorUpgrader(u MajorUpgrader) Option

WithMajorUpgrader enables the major upgrade routes; without it they return 501.

func WithMasterKeyRotation ¶

func WithMasterKeyRotation(r MasterKeyRotator, masterKeyFilePath string) Option

WithMasterKeyRotation enables POST /api/v1/system/master-key/rotate and the doctor's rotation-age check. masterKeyFilePath should be the on-disk path the running control plane loaded its master key from, or "" if it came from APP_MASTER_KEY instead: see MasterKeyRotator's own doc comment for what that distinction changes about a rotation's response. Without this option configured (the default), the route returns 501, the same shape WithSecretSetter's own absence produces.

func WithMinAgentVersion ¶ added in v0.2.0

func WithMinAgentVersion(v string) Option

WithMinAgentVersion flags nodes whose agent reports an older version. Empty disables the check.

func WithModels ¶ added in v0.2.0

func WithModels(s ModelService) Option

WithModels enables the /api/v1/models routes. Without it they return 501.

func WithMultiNode ¶ added in v0.2.0

func WithMultiNode(s PlacementStore, cfg nodemove.Config, meshPortMin, meshPortMax int) Option

WithMultiNode enables persisted node moves, drains, labels and selectors. Without it those routes return 501 and drain keeps no history.

func WithNetworkShareSecrets ¶ added in v0.2.0

func WithNetworkShareSecrets(s NetworkShareSecretsSetter) Option

WithNetworkShareSecrets enables POST/PUT /api/v1/network-shares for a cifs share. Without one configured (the default), those return 501; GET, DELETE, and an nfs share's create/update work regardless, the same shape WithRegistryCredentialSecrets establishes.

func WithNewDeviceApproval ¶ added in v0.2.0

func WithNewDeviceApproval(on bool) Option

WithNewDeviceApproval pauses a password login from an untrusted browser until another live session of the account approves it.

func WithNodeAlertThresholds ¶

func WithNodeAlertThresholds(patchStatus, nodeDiskSpace, nodeCPU, nodeMemory float64) Option

WithNodeAlertThresholds sets the thresholds GET /api/v1/nodes/{id} uses for its live alert_status section, the same values cmd/levelrail/main.go already resolves (from APP_ALERT_PATCH_STATUS_THRESHOLD, APP_ALERT_NODE_DISK_SPACE_THRESHOLD_PERCENT, APP_ALERT_NODE_CPU_THRESHOLD_PERCENT, APP_ALERT_NODE_MEMORY_THRESHOLD_BYTES) and passes to alerting.NewEngine, so a rule's scheduled evaluation and a node's on-demand check never disagree.

func WithNodeCertThresholds ¶ added in v0.2.0

func WithNodeCertThresholds(warning, critical time.Duration) Option

WithNodeCertThresholds sets when a node's agent certificate is reported as expiring (warning) and critical. Zero keeps the alerting defaults.

func WithNodeJoinTokenTTL ¶ added in v0.2.0

func WithNodeJoinTokenTTL(d time.Duration) Option

WithNodeJoinTokenTTL sets how long a dashboard or CLI join token stays redeemable, clamped to [1m, 24h]. Zero keeps the 15 minute default.

func WithNodeProviderSecrets ¶ added in v0.2.0

func WithNodeProviderSecrets(s NodeProviderSecrets) Option

WithNodeProviderSecrets enables POST /api/v1/node-providers and every node-providers/{provider}/... route. Without it (the default), those routes return 501, the same shape WithCloudflareDNSSecrets establishes for its own credential.

func WithNodeProvisionerFactory ¶ added in v0.2.0

func WithNodeProvisionerFactory(f NodeProvisionerFactory) Option

WithNodeProvisionerFactory overrides how a NodeProvisioner is built from a provider name and resolved token, for this package's own tests. Production callers leave this unset; NewRouter's default, defaultNodeProvisionerFactory, builds real internal/provision clients.

func WithNodeProvisions ¶ added in v0.2.0

func WithNodeProvisions(s NodeProvisionStore) Option

WithNodeProvisions overrides the node provision store NewRouter otherwise wires to s (the *store.DB passed to NewRouter, which satisfies NodeProvisionStore structurally). This package's own tests use it to inject a store that fails a specific call, for the half-succeeded-write path handleCreateNodeProvision must handle.

func WithNodeSessionCloser ¶ added in v0.2.0

func WithNodeSessionCloser(c NodeSessionCloser) Option

WithNodeSessionCloser lets certificate revocation close the node's live session immediately.

func WithNotificationChannelTester ¶

func WithNotificationChannelTester(t NotificationChannelTester) Option

WithNotificationChannelTester enables the test-send routes: a real send, not just a format check. Without one, they return 501.

func WithNotificationChannels ¶

func WithNotificationChannels(c NotificationChannels) Option

WithNotificationChannels enables the notification-channel CRUD routes. Without one configured (the default), they return 501.

func WithNotificationDeliveries ¶

func WithNotificationDeliveries(d NotificationDeliveryStore) Option

WithNotificationDeliveries enables the delivery-history route and records a delivery row for every existing-channel test-send. Without one, the deliveries route returns 501 and test-send simply skips recording.

func WithOAuthSecrets ¶

func WithOAuthSecrets(s OAuthSecrets) Option

WithOAuthSecrets enables OAuth sign-in end to end. Without one configured (the default), those routes return 501, the same "not configured" shape WithSecretSetter's absence produces.

func WithOrphanedContainerManager ¶ added in v0.2.0

func WithOrphanedContainerManager(m OrphanedContainerManager) Option

WithOrphanedContainerManager enables POST /api/v1/system/containers/{name}/stop and .../remove. Without one configured (the default), both routes return 501, the same "not configured" shape WithOrphanedVolumeManager's absence produces.

func WithOrphanedVolumeManager ¶ added in v0.2.0

func WithOrphanedVolumeManager(m OrphanedVolumeManager) Option

WithOrphanedVolumeManager enables GET /api/v1/system/volumes/orphaned and POST /api/v1/system/volumes/orphaned/cleanup. Without one configured (the default), both routes return 501, the same "not configured" shape WithDockerPruner's absence produces.

func WithPITRRestoreRunner ¶ added in v0.2.0

func WithPITRRestoreRunner(r PITRRestoreRunner) Option

WithPITRRestoreRunner enables POST /api/v1/databases/{name}/pitr-restore and GET /api/v1/databases/{name}/pitr's own live window computation. Without one configured (the default), the restore route returns 501 and the status route reports enabled/enabled_at only, no window: the same "not configured" shape WithRestoreRunner's absence produces.

func WithPipelines ¶ added in v0.2.0

func WithPipelines(st PipelineStore, runner PipelineRunner) Option

WithPipelines enables the pipeline endpoints. runner may be nil, in which case definitions can be edited but starting, cancelling, and re-running return 501.

func WithPreviewLimits ¶ added in v0.2.0

func WithPreviewLimits(l PreviewLimits) Option

WithPreviewLimits sets the per-app and global preview caps (cmd/levelrail reads APP_PREVIEW_ENV_MAX_PER_APP and APP_PREVIEW_ENV_MAX_TOTAL).

func WithPreviewStuckAfter ¶ added in v0.2.0

func WithPreviewStuckAfter(d time.Duration) Option

WithPreviewStuckAfter sets how long a preview may stay in deploying before the startup sweep marks it failed. Zero keeps the default.

func WithPreviewTTL ¶

func WithPreviewTTL(d time.Duration) Option

WithPreviewTTL overrides how long a preview environment can go without a webhook update before SweepStalePreviewEnvironments tears it down. Without one configured (or passed as 0), defaultPreviewTTL (7 days) applies. Same "no hardcoded thresholds, use env vars" shape as WithCertExpiryWarningWindow: this package never reads the environment directly, cmd/levelrail/main.go reads APP_PREVIEW_TTL and passes the parsed duration here.

func WithPublicHost ¶

func WithPublicHost(host string) Option

WithPublicHost sets the IP or hostname GET /api/v1/apps/{name}/domains/{domain}/check tells an operator to point their DNS record at (domain_check.go's advertisedHost). Without one configured (the default), that handler falls back to a best-effort guess from the request's own Host header instead of failing: this package never reads the environment directly, cmd/levelrail/main.go reads APP_PUBLIC_HOST and passes it here, the same shape WithCertExpiryWarningWindow already follows for its own env var.

func WithPublicHostSource ¶ added in v0.2.0

func WithPublicHostSource(source string) Option

WithPublicHostSource records how the public host was found ("env", "detected", "disabled" or "none"), shown on the ingress settings page.

func WithPushVAPIDPublicKey ¶ added in v0.2.0

func WithPushVAPIDPublicKey(publicKey string) Option

WithPushVAPIDPublicKey enables browser push notifications: GET .../push-subscriptions/vapid-public-key returns publicKey, and POST .../push-subscriptions accepts new registrations. Without one configured (the default, no master key set), both return 501: the VAPID private key half can only ever be stored through secretsManager.

func WithReadinessProbes ¶ added in v0.2.0

func WithReadinessProbes(p ReadinessProbes) Option

WithReadinessProbes enables the database, migration and engine checks on GET /readyz.

func WithReconcileNudger ¶ added in v0.2.0

func WithReconcileNudger(n ReconcileNudger) Option

WithReconcileNudger lets desired-state-changing handlers (app/database create, stop, start, restart, delete) request an immediate reconcile pass right after they save, instead of every such action waiting up to a full resyncInterval to visibly take effect. Without one configured (the default), those handlers still work exactly as before: the resync ticker and Docker's own event stream are what converge desired and observed state regardless.

func WithRegistryAuthTester ¶

func WithRegistryAuthTester(t RegistryAuthTester) Option

WithRegistryAuthTester enables POST /api/v1/registry-credentials/{id}/test. Without one configured (the default), that route returns 501, the same shape WithDockerPinger's own absence produces for GET /api/v1/system/status.

func WithRegistryCatalogSecrets ¶ added in v0.2.0

func WithRegistryCatalogSecrets(s RegistryCatalogSecrets) Option

WithRegistryCatalogSecrets enables GET /api/v1/registry/repositories and GET /api/v1/registry/tags. Without one configured (the default), both return 501, the same shape WithRegistrySecrets establishes.

func WithRegistryCredentialSecrets ¶

func WithRegistryCredentialSecrets(s RegistryCredentialSecretsSetter) Option

WithRegistryCredentialSecrets enables POST /api/v1/registry-credentials. Without one configured (the default), that route returns 501; GET and DELETE work regardless, the same shape WithBackupSecrets establishes.

func WithRegistrySecrets ¶

func WithRegistrySecrets(s RegistrySecrets) Option

WithRegistrySecrets enables PUT/DELETE /api/v1/settings/registry. Without one configured (the default), both return 501; GET works regardless, the same shape WithCloudflareTunnelSecrets establishes.

func WithRequestLogThresholds ¶ added in v0.2.0

func WithRequestLogThresholds(slow, critical time.Duration) Option

WithRequestLogThresholds sets requestLoggingMiddleware's Warn/Error duration bands. Either argument <= 0 keeps that band's own default. cmd/levelrail/main.go resolves APP_SLOW_REQUEST_THRESHOLD/ APP_CRITICAL_REQUEST_THRESHOLD and calls this unconditionally, the same "no hardcoded thresholds" convention WithSessionTTL's own doc comment establishes.

func WithRequestSummaryWindow ¶ added in v0.2.0

func WithRequestSummaryWindow(d time.Duration) Option

WithRequestSummaryWindow sets the lookback of the request summary attached to GET /api/v1/apps/{name}; non-positive keeps the 5 minute default.

func WithResourceRecommendationLookback ¶

func WithResourceRecommendationLookback(d time.Duration) Option

WithResourceRecommendationLookback overrides how far back GET /api/v1/apps/{name}/resource-recommendation looks for usage history (handleAppResourceRecommendation). Without one configured (or passed as 0), defaultResourceRecommendationLookback (7 days) applies. Same "no hardcoded thresholds, use env vars" shape as WithSessionTTL/ WithCertExpiryWarningWindow: this package never reads the environment directly, cmd/levelrail/main.go reads APP_RESOURCE_RECOMMENDATION_LOOKBACK and passes the parsed duration here.

func WithRestoreRunner ¶

func WithRestoreRunner(r RestoreRunner) Option

WithRestoreRunner enables POST /api/v1/databases/{name}/restore. Without one configured (the default), that route returns 501, the same "not configured" shape WithBackupRunner's absence produces; listing restore history (GET .../restores) works regardless, the same "listing needs no live runner" reasoning WithBackupRunner's own doc comment gives for backup history.

func WithRoute53DNSCredentialResolver ¶ added in v0.2.0

func WithRoute53DNSCredentialResolver(r Route53DNSCredentialResolver) Option

WithRoute53DNSCredentialResolver is the same shape as WithCloudflareDNSTokenResolver for the Route53 access key pair.

func WithRoute53DNSSecrets ¶ added in v0.2.0

func WithRoute53DNSSecrets(s Route53DNSSecrets) Option

WithRoute53DNSSecrets enables PUT/DELETE /api/v1/settings/route53-dns. Without one configured (the default), both return 501; GET works regardless, the same shape WithCloudflareDNSSecrets establishes for its own, independent ACME DNS-01 provider.

func WithSSHNodeProvisions ¶ added in v0.2.0

func WithSSHNodeProvisions(s SSHNodeProvisionStore) Option

WithSSHNodeProvisions overrides the SSH node provision store NewRouter otherwise wires to s (the *store.DB passed to NewRouter, which satisfies SSHNodeProvisionStore structurally), the same "seam for a failing fake" shape WithNodeProvisions establishes for the cloud path.

func WithSSHProvisioner ¶ added in v0.2.0

func WithSSHProvisioner(p SSHProvisioner) Option

WithSSHProvisioner overrides the SSH provisioner NewRouter otherwise defaults to (sshprovision.New()), for this package's own tests: a fake that never dials a real network.

func WithScheduledTaskRunner ¶

func WithScheduledTaskRunner(r ScheduledTaskRunner) Option

WithScheduledTaskRunner enables POST /api/v1/apps/{name}/scheduled-tasks/{id}/run. Without one configured (the default), that route returns 501: scheduled task CRUD still works either way (rt.scheduledTasks is always set), only actually running one on demand needs this. r should be the exact same *scheduledtask.Runner instance cmd/levelrail/main.go wires into scheduledtask.NewScheduler, the same "construct once, share by reference" reasoning WithBackupRunner's own doc comment gives for backupRunner.

func WithSecretBinding ¶ added in v0.2.0

func WithSecretBinding(b SecretBinder) Option

WithSecretBinding enables GET /api/v1/system/secrets/binding, POST /api/v1/system/secrets/rebind, the doctor's secret_binding check, and the automatic rebind after a master key rotation.

func WithSecretRotationWarnAge ¶ added in v0.2.0

func WithSecretRotationWarnAge(d time.Duration) Option

WithSecretRotationWarnAge overrides how old a secret's last-set value can get before it is flagged as due for rotation (GET /apps/{name}/secrets, GET .../env/all, and the doctor's stale_secrets check). Without one configured (or passed as 0), defaultSecretRotationWarnAge (90 days) applies. Same "no hardcoded thresholds, use env vars" shape as WithAuditLogRetention: this package never reads the environment directly, cmd/levelrail/main.go reads APP_SECRET_ROTATION_WARN_DAYS and passes the parsed duration here.

func WithSecretSetter ¶

func WithSecretSetter(s SecretSetter) Option

WithSecretSetter enables PUT /api/v1/apps/{name}/secrets/{key}. Without one configured (the default), that route returns 501: an operator running Levelrail without APP_MASTER_KEY set gets a clear "not configured" response instead of the route not existing at all or silently discarding the value.

func WithSelfUpgrade ¶ added in v0.2.0

func WithSelfUpgrade(s SelfUpgradeStore, launch SelfUpgradeLauncher) Option

WithSelfUpgrade enables the self-upgrade plan, start and attempts routes. launch may be nil to use systemd-run on a root host.

func WithServiceVolumeBackupRunner ¶

func WithServiceVolumeBackupRunner(r ServiceVolumeBackupRunner) Option

WithServiceVolumeBackupRunner enables POST /api/v1/apps/{name}/volumes/{volume}/backups. Without one configured (the default), that route returns 501, the same "not configured" shape WithBackupRunner's absence produces; listing history and reading the schedule work regardless, the same "no live runner needed" reasoning WithBackupRunner's own doc comment gives.

func WithServiceVolumeRestoreRunner ¶

func WithServiceVolumeRestoreRunner(r ServiceVolumeRestoreRunner) Option

WithServiceVolumeRestoreRunner enables POST /api/v1/apps/{name}/volumes/{volume}/restore. Without one configured (the default), that route returns 501, the same "not configured" shape WithRestoreRunner's absence produces.

func WithSessionTTL ¶

func WithSessionTTL(d time.Duration) Option

WithSessionTTL overrides how long a session cookie stays valid. Without one configured, defaultSessionTTL (24h) applies. The project's "no hardcoded thresholds, use env vars" rule is honored one layer up, at cmd/levelrail/main.go, which reads APP_SESSION_TTL and passes the parsed duration here; this package itself never reads the environment directly (NewRouter takes everything as constructor args, matching every other option here).

func WithStaleSecretCounter ¶ added in v0.2.0

func WithStaleSecretCounter(c StaleSecretCounter) Option

WithStaleSecretCounter enables the stale_secrets check on GET /api/v1/system/doctor. Without one configured (the default), that check reports unknown, the same "optional signal, absence is not an error" shape WithDBPinger's own absence already has.

func WithStatusPage ¶ added in v0.2.0

func WithStatusPage(db StatusPageDB, cfg statuspage.Config, perMinute int) Option

WithStatusPage enables the status page management routes, the public page and the health sampler (see RunStatusPageSampler). perMinute is the per-client-IP budget on public reads.

func WithStatusPageViewer ¶ added in v0.2.0

func WithStatusPageViewer(st StatusPageStore, v StatusPageViewer, perMinute int) Option

WithStatusPageViewer wires a custom view builder, for tests.

func WithStorage ¶ added in v0.2.0

func WithStorage(d StorageDeps) Option

WithStorage enables the /api/v1/storage and /api/v1/log-archive routes. Without it they return 501.

func WithTelemetryQuerier ¶

func WithTelemetryQuerier(q TelemetryQuerier) Option

WithTelemetryQuerier enables GET /api/v1/apps/{name}/metrics and GET /api/v1/apps/{name}/logs. Without one configured, both routes return 501, the same "not configured" shape WithSecretSetter's absence produces, rather than the routes not existing at all or panicking on a nil dereference.

func WithTokenRedeemRateLimit ¶ added in v0.2.0

func WithTokenRedeemRateLimit(perMinute int) Option

WithTokenRedeemRateLimit enables a per-client-IP budget on the unauthenticated POST /api/v1/auth/reset-password and POST /api/v1/invites/accept routes. perMinute <= 0 disables it.

func WithUpgradeHistory ¶ added in v0.2.0

func WithUpgradeHistory(s UpgradeHistoryStore) Option

WithUpgradeHistory enables GET /api/v1/updates/history and the ack route. Without one the history is empty and acknowledging answers 501.

func WithVaultSecrets ¶ added in v0.2.0

func WithVaultSecrets(s VaultSecrets) Option

WithVaultSecrets enables PUT/DELETE /api/v1/settings/vault. Without one configured (the default), both return 501; GET works regardless, the same shape WithCloudflareTunnelSecrets establishes.

func WithVolumeCloneRestoreRunner ¶

func WithVolumeCloneRestoreRunner(r VolumeCloneRestoreRunner) Option

WithVolumeCloneRestoreRunner enables POST /api/v1/apps/{name}/volumes/{volume}/restore-as-new. Without one configured (the default), that route returns 501, the same "not configured" shape WithServiceVolumeRestoreRunner's absence produces; listing clone-restore history (GET .../clone-restores) works regardless, the same "listing needs no live runner" reasoning WithCloneRestoreRunner's own doc comment gives.

func WithWakeToken ¶ added in v0.2.0

func WithWakeToken(token string) Option

WithWakeToken sets the secret the ingress wake route presents to the control plane. Without one, the wake hook answers 404 for everyone.

func WithWebhookDeliveryRetention ¶ added in v0.2.0

func WithWebhookDeliveryRetention(d time.Duration) Option

WithWebhookDeliveryRetention overrides how long a webhook_deliveries row survives before PurgeOldWebhookDeliveries removes it. Without one configured (or passed as 0), defaultWebhookDeliveryRetention (30 days) applies. Same "no hardcoded thresholds, use env vars" shape as WithAuditLogRetention: this package never reads the environment directly, cmd/levelrail/main.go reads APP_WEBHOOK_DELIVERY_RETENTION_DAYS and passes the parsed duration here.

func WithWebhookRateLimit ¶ added in v0.2.0

func WithWebhookRateLimit(perMinute int) Option

WithWebhookRateLimit enables a per-(client IP, app name) budget on POST /api/v1/webhooks/github/{name} (git_webhook.go), checked before that route's DB lookup, secret resolution, and delivery-history write: a request rejected here never reaches any of that work. perMinute <= 0 disables it, the same "nil is valid" shape WithAPIRateLimit's own absence establishes; without this option (the default), the route is unthrottled. cmd/levelrail/main.go reads APP_WEBHOOK_RATE_LIMIT_RPM and calls this unconditionally with its resolved (env-or-default) value, so the real control plane is always protected even when an operator never sets the env var.

type OrganizationStore ¶

type OrganizationStore interface {
	SaveOrganization(ctx context.Context, o store.Organization) error
	GetOrganization(ctx context.Context, id string) (store.Organization, error)
	ListOrganizations(ctx context.Context) ([]store.Organization, error)
	DeleteOrganization(ctx context.Context, id string) error
	SetProjectOrganization(ctx context.Context, projectID, orgID string) error
	// SetOrganizationEnvVars/ListOrganizationEnvVars back GET/PUT
	// /api/v1/organizations/{id}/env (organization_env.go): shared env
	// vars every project filed under this organization inherits as the
	// base layer beneath its own project_env_vars tier
	// (internal/reconcile/application's resolveEnv), full-replace on
	// write, mirroring ProjectStore's SetProjectEnvVars/ListProjectEnvVars
	// one level up.
	SetOrganizationEnvVars(ctx context.Context, orgID string, vars map[string]string) error
	ListOrganizationEnvVars(ctx context.Context, orgID string) (map[string]string, error)
	// ListOrganizationEnvVarsDetailed/SetOrganizationSecretEnvVar/
	// DeleteOrganizationSecretEnvVar/ListOrganizationSecretEnvKeys back
	// GET /api/v1/organizations/{id}/env/all and the secret-var
	// sub-resource routes (organization_env.go), mirroring ProjectStore's
	// own secret-capable extension one tier down.
	ListOrganizationEnvVarsDetailed(ctx context.Context, orgID string) ([]store.SharedEnvVar, error)
	SetOrganizationSecretEnvVar(ctx context.Context, orgID, key string) error
	DeleteOrganizationSecretEnvVar(ctx context.Context, orgID, key string) error
	ListOrganizationSecretEnvKeys(ctx context.Context, orgID string) ([]string, error)
}

OrganizationStore is the store surface the organizations handlers need.

type OrphanSweepResult ¶ added in v0.2.0

type OrphanSweepResult struct {
	StuckDeploys     int
	OrphanedServices int
}

OrphanSweepResult counts what SweepOrphanPreviews repaired.

type OrphanedContainerManager ¶ added in v0.2.0

type OrphanedContainerManager interface {
	Stop(ctx context.Context, id string, timeout time.Duration) error
	Remove(ctx context.Context, id string, force bool) error
}

OrphanedContainerManager is the surface POST /api/v1/system/containers/{name}/stop and .../remove need: stop or remove one container by ID. Unlike ContainerLister, which only reads, this is the mutating half, the same read/destructive-action split DockerDiskUsager/DockerPruner and OrphanedVolumeManager's own list/remove pair already establish. Deliberately narrower than docker.Runtime (just the two methods these routes call), the same "consumer-defined boundary, no second divergent surface" reasoning DockerPinger/ImageLister give; *docker.Client satisfies this structurally via Stop and Remove (internal/docker/client.go).

isManagedContainer (containers_orphaned.go), not this package's callers of this interface, decides which containers these methods may ever be invoked against: every route calling it re-confirms a container is genuinely orphaned before calling Stop/Remove, never trusting a client-supplied flag.

type OrphanedVolumeManager ¶ added in v0.2.0

type OrphanedVolumeManager interface {
	ListNamedVolumes(ctx context.Context) ([]docker.NamedVolume, error)
	RemoveVolume(ctx context.Context, name string) error
}

OrphanedVolumeManager is the surface GET /api/v1/system/volumes/orphaned and POST /api/v1/system/volumes/orphaned/cleanup need: list this instance's own named (non-anonymous) Docker volumes, and remove one by exact name. Unlike DockerPruner, this package (not internal/docker) decides which of ListNamedVolumes' results are actually orphaned, by cross- referencing against current desired state (computeOrphanedVolumes in volumes_orphaned.go): internal/docker stays store-agnostic, the same split PruneContainers' own keep-list parameter already establishes. *docker.Client satisfies this structurally via ListNamedVolumes and RemoveVolume (internal/docker/volumes_orphaned.go).

type PITRRestoreHistoryStore ¶ added in v0.2.0

type PITRRestoreHistoryStore interface {
	ListPITRRestoreHistory(ctx context.Context, databaseName string) ([]store.PITRRestoreHistory, error)
}

PITRRestoreHistoryStore is the store surface the PITR restore history handler needs.

type PITRRestoreRunner ¶ added in v0.2.0

type PITRRestoreRunner interface {
	// WindowBounds returns the same information *backup.PITRRunner.Window
	// does, destructured into plain values instead of that method's own
	// PITRWindow struct: an interface method's return type must be
	// identical across a package boundary, and internal/api never
	// imports internal/backup's types directly (databaseContainerName's
	// own doc comment describes this boundary), so a struct return here
	// would need its own separately-declared, ever-so-slightly duplicate
	// type for no real benefit over returning the three values plain.
	WindowBounds(ctx context.Context, databaseName, containerName string) (hasBaseBackup bool, start, end time.Time, err error)
	ValidateTarget(ctx context.Context, databaseName, containerName string, target time.Time) error
	RunPITRRestore(ctx context.Context, historyID, databaseName, containerName, dataVolumeName, baseBackupHistoryID string, targetTime time.Time) error
}

PITRRestoreRunner is the surface the PITR restore endpoints need from internal/backup.PITRRunner: compute the currently recoverable window, validate a target timestamp against it, and run one restore attempt end to end. *backup.PITRRunner satisfies this structurally, the same boundary BaseBackupRunner above describes.

type PipelineEvents ¶ added in v0.2.0

type PipelineEvents interface {
	TriggerEvent(ctx context.Context, app string, ev pipeline.Event, ref, sha, actor string) []store.PipelineRun
}

PipelineEvents starts runs of every matching pipeline for a git event. *pipeline.Engine satisfies it.

type PipelineOverviewStore ¶ added in v0.2.0

type PipelineOverviewStore interface {
	ListPipelineRunRows(ctx context.Context, f store.PipelineRunFilter) ([]store.PipelineRunRow, error)
	PipelineAppCounts(ctx context.Context, since time.Time) ([]store.PipelineAppCounts, error)
}

PipelineOverviewStore is the cross-app read surface behind the Pipelines overview. *store.DB satisfies it.

type PipelineRunner ¶ added in v0.2.0

type PipelineRunner interface {
	Start(ctx context.Context, p store.Pipeline, opt pipeline.StartOptions) (store.PipelineRun, error)
	Cancel(ctx context.Context, runID string) error
	Rerun(ctx context.Context, runID, actor string) (store.PipelineRun, error)
	// DecideHold releases or rejects a run held for approval.
	DecideHold(ctx context.Context, runID string, approved bool, by string) (bool, error)
	Nudge()
}

PipelineRunner starts, cancels, and re-runs pipeline runs. *pipeline.Engine satisfies it.

type PipelineStore ¶ added in v0.2.0

type PipelineStore interface {
	SavePipeline(ctx context.Context, p store.Pipeline) (store.Pipeline, error)
	GetPipelineByName(ctx context.Context, app, name string) (store.Pipeline, error)
	ListPipelines(ctx context.Context, app string) ([]store.Pipeline, error)
	DeletePipeline(ctx context.Context, id string) error
	ListPipelineRuns(ctx context.Context, app, pipelineID string, limit int) ([]store.PipelineRun, error)
	GetPipelineRun(ctx context.Context, id string) (store.PipelineRun, error)
	ListPipelineJobs(ctx context.Context, runID string) ([]store.PipelineJob, error)
	ListPipelineLogs(ctx context.Context, runID, jobKey string, afterID int64, limit int) ([]store.PipelineLogLine, error)
	ListPipelineStepLogs(ctx context.Context, runID, jobKey string, step *int, afterID int64, limit int) ([]store.PipelineLogLine, error)
	ListPipelineApprovals(ctx context.Context, runID string) ([]store.PipelineApproval, error)
	DecidePipelineApproval(ctx context.Context, id int64, decision, by, comment string, now time.Time) (bool, error)
	ListPipelineTriggerLog(ctx context.Context, app string, limit int) ([]store.PipelineTriggerLog, error)
}

PipelineStore is the store surface the pipeline handlers need.

type PipelineSyncStore ¶ added in v0.2.0

type PipelineSyncStore interface {
	GetPipelineSync(ctx context.Context, app string) (store.PipelineSync, error)
	SavePipelineSync(ctx context.Context, s store.PipelineSync) error
}

PipelineSyncStore holds the per-app sync settings. *store.DB satisfies it.

type PipelineSyncer ¶ added in v0.2.0

type PipelineSyncer interface {
	Sync(ctx context.Context, app string) (pipeline.SyncResult, error)
	SyncOnPush(ctx context.Context, app, ref, sha string) (pipeline.SyncResult, bool, error)
}

PipelineSyncer copies an app's repository pipeline files into its saved definitions. *pipeline.Syncer satisfies it.

type PlacementStore ¶ added in v0.2.0

type PlacementStore interface {
	nodemove.Store
	ListNodeLabels(ctx context.Context) (map[string]map[string]string, error)
	GetNodeLabels(ctx context.Context, nodeID string) (map[string]string, error)
	SetNodeLabels(ctx context.Context, nodeID string, labels map[string]string) error
	GetPlacementSelector(ctx context.Context, kind, resource string) (map[string]string, error)
	SetPlacementSelector(ctx context.Context, kind, resource string, selector map[string]string) error
	EnsureDatabaseMeshPort(ctx context.Context, name string, minPort, maxPort int) (int, error)
}

PlacementStore is the persistence multi-node placement needs; *store.DB satisfies it.

type PolicyStore ¶

type PolicyStore interface {
	SavePolicy(ctx context.Context, p store.Policy) error
	UpdatePolicy(ctx context.Context, id, name, description, document string) error
	GetPolicy(ctx context.Context, id string) (*store.Policy, error)
	ListPolicies(ctx context.Context) ([]store.Policy, error)
	DeletePolicy(ctx context.Context, id string) error
	AttachPolicy(ctx context.Context, id, policyID, principalType, principalID string) error
	DetachPolicy(ctx context.Context, policyID, principalType, principalID string) error
	ListPoliciesForPrincipal(ctx context.Context, principalType, principalID string) ([]store.Policy, error)
	ListAttachmentsForPolicy(ctx context.Context, policyID string) ([]store.PolicyAttachment, error)
}

PolicyStore is the store surface the IAM policy handlers need, defined here next to the handlers that use it, the same "single-file feature" shape OnboardingStore/AuditStore already use rather than every store interface living in store_interfaces.go.

type PreviewEnvironmentStore ¶

type PreviewEnvironmentStore interface {
	SavePreviewEnvironment(ctx context.Context, p store.PreviewEnvironment) error
	UpdatePreviewEnvironment(ctx context.Context, p store.PreviewEnvironment) error
	GetPreviewEnvironmentByAppAndPR(ctx context.Context, appName string, prNumber int) (*store.PreviewEnvironment, error)
	ListPreviewEnvironmentsByApp(ctx context.Context, appName string) ([]store.PreviewEnvironment, error)
	DeletePreviewEnvironment(ctx context.Context, id string) error
	ListStalePreviewEnvironments(ctx context.Context, cutoff time.Time) ([]store.PreviewEnvironment, error)
	ListPreviewEnvironments(ctx context.Context) ([]store.PreviewEnvironment, error)
	SetPreviewEnvironmentCommentID(ctx context.Context, id string, commentID int64) error
	SetPreviewEnvironmentExtendedUntil(ctx context.Context, id, until string) error
	GetPreviewAppSettings(ctx context.Context, appName string) (store.PreviewAppSettings, error)
	ListPreviewAppSettings(ctx context.Context) (map[string]store.PreviewAppSettings, error)
	SavePreviewAppSettings(ctx context.Context, s store.PreviewAppSettings) error

	// SavePreviewEphemeralDatabase through DeletePreviewEphemeralDatabase
	// back the ephemeralInPreviews lifecycle
	// (preview_environments_databases.go): provisioning a preview-scoped
	// database instance, listing them for the API's own status surface,
	// and tearing them down alongside the preview that owns them.
	SavePreviewEphemeralDatabase(ctx context.Context, p store.PreviewEphemeralDatabase) error
	GetPreviewEphemeralDatabaseByPreviewAndKey(ctx context.Context, previewEnvironmentID, sourceKey string) (*store.PreviewEphemeralDatabase, error)
	ListPreviewEphemeralDatabasesByPreview(ctx context.Context, previewEnvironmentID string) ([]store.PreviewEphemeralDatabase, error)
	UpdatePreviewEphemeralDatabaseStatus(ctx context.Context, id, status, statusReason, updatedAt string) error
	DeletePreviewEphemeralDatabase(ctx context.Context, id string) error

	// SavePreviewDatabaseIsolation through DeletePreviewDatabaseIsolation
	// back the isolatedInPreviews lifecycle
	// (preview_environments_database_isolation.go): an isolated Postgres
	// role on an existing database, one per (preview, sourceKey).
	SavePreviewDatabaseIsolation(ctx context.Context, p store.PreviewDatabaseIsolation) error
	GetPreviewDatabaseIsolationByPreviewAndKey(ctx context.Context, previewEnvironmentID, sourceKey string) (*store.PreviewDatabaseIsolation, error)
	ListPreviewDatabaseIsolationsByPreview(ctx context.Context, previewEnvironmentID string) ([]store.PreviewDatabaseIsolation, error)
	UpdatePreviewDatabaseIsolationStatus(ctx context.Context, id, status, statusReason, updatedAt string) error
	DeletePreviewDatabaseIsolation(ctx context.Context, id string) error
}

PreviewEnvironmentStore is the store surface preview environment webhook handling and its HTTP routes (preview_environments_handlers.go) need. *store.DB satisfies this structurally.

type PreviewLimits ¶ added in v0.2.0

type PreviewLimits struct {
	MaxPerApp int
	MaxTotal  int
}

PreviewLimits caps simultaneous live preview environments. Zero means unlimited for that scope.

type PreviewService ¶ added in v0.2.0

type PreviewService interface {
	Config() preview.Config
	Settings(ctx context.Context, app string) (preview.AppSettings, error)
	SaveSettings(ctx context.Context, app string, patch preview.SettingsPatch) (preview.AppSettings, error)
	List(ctx context.Context, app string) ([]preview.Record, error)
	OpenImage(ctx context.Context, app, deploymentID string) (string, preview.Record, error)
	Capture(ctx context.Context, app string) error
	Prune(ctx context.Context, app string, all bool) (preview.PruneResult, error)
	Status(ctx context.Context, app string) (preview.Summary, error)
	DeleteApp(ctx context.Context, app string) error
}

PreviewService is the deploy preview surface; *preview.Manager satisfies it.

type ProbeAttemptStore ¶ added in v0.2.0

type ProbeAttemptStore interface {
	ListProbeAttempts(ctx context.Context, deployID string) ([]store.ProbeAttempt, error)
}

ProbeAttemptStore is the store surface GET /api/v1/apps/{name}/deploys/{deployId}/probes needs: the individual readiness-probe attempts (migrations/0284_probe_attempts.sql, internal/probe.WithOnAttempt) one deploy attempt's cutover made. *store.DB satisfies this structurally.

type ProjectStore ¶

type ProjectStore interface {
	SaveProject(ctx context.Context, p store.Project) error
	GetProject(ctx context.Context, id string) (store.Project, error)
	ListProjects(ctx context.Context) ([]store.Project, error)
	DeleteProject(ctx context.Context, id string) error
	// SetProjectEnvVars/ListProjectEnvVars back GET/PUT
	// /api/v1/projects/{id}/env (project_env.go): shared env vars every
	// app filed under this project inherits as its resolveEnv base
	// layer (internal/reconcile/application), full-replace on write, the
	// same shape PUT /api/v1/apps/{name}'s own env field already has.
	SetProjectEnvVars(ctx context.Context, projectID string, vars map[string]string) error
	ListProjectEnvVars(ctx context.Context, projectID string) (map[string]string, error)
	// ListProjectEnvVarsDetailed/SetProjectSecretEnvVar/
	// DeleteProjectSecretEnvVar/ListProjectSecretEnvKeys back GET
	// /api/v1/projects/{id}/env/all and the secret-var sub-resource
	// routes (project_env.go): the secret-capable extension of the
	// plain-only pair above, reusing internal/secrets.Manager (via
	// Router.secrets) for the actual encryption rather than a second
	// path.
	ListProjectEnvVarsDetailed(ctx context.Context, projectID string) ([]store.SharedEnvVar, error)
	SetProjectSecretEnvVar(ctx context.Context, projectID, key string) error
	DeleteProjectSecretEnvVar(ctx context.Context, projectID, key string) error
	ListProjectSecretEnvKeys(ctx context.Context, projectID string) ([]string, error)
}

ProjectStore is the store surface the projects handlers need (projects.go). See that file's own package doc comment for why a project is deliberately not the start of the deferred Phase 4 teams/ RBAC work (repo-plan section 6's own scope note): no owner, no member list, no per-project ability, just create/list/get/delete.

type ProxyIntegrationStore ¶ added in v0.2.0

type ProxyIntegrationStore interface {
	GetProxyIntegrationSettings(ctx context.Context) (store.ProxyIntegrationSettings, error)
	UpdateProxyIntegrationSettings(ctx context.Context, s store.ProxyIntegrationSettings) error
	ListProxyRouteStatus(ctx context.Context) ([]store.ProxyRouteStatus, error)
	SaveProxyRouteVerification(ctx context.Context, s store.ProxyRouteStatus) error
}

ProxyIntegrationStore is the persistence the proxy integration routes need beyond what the syncer reads.

type PushSubscriptions ¶ added in v0.2.0

type PushSubscriptions interface {
	SavePushSubscription(ctx context.Context, s store.PushSubscription) error
	ListPushSubscriptionsForUser(ctx context.Context, userID string) ([]store.PushSubscription, error)
	DeletePushSubscription(ctx context.Context, id, userID string) error
}

PushSubscriptions is the store surface the handlers below need. *store.DB satisfies this structurally.

type ReadinessProbes ¶ added in v0.2.0

type ReadinessProbes struct {
	Database      func(ctx context.Context) error
	Migrations    func(ctx context.Context) error
	EngineStarted func() bool
}

ReadinessProbes are the cheap checks GET /readyz runs. A nil probe is skipped.

type ReconcileNudger ¶ added in v0.2.0

type ReconcileNudger interface {
	Nudge()
}

ReconcileNudger is *reconcile.Engine's own Nudge method, narrowed the same consumer-defined way DockerPinger is above: a handler that just changed desired state calls it to request an immediate reconcile pass instead of leaving an operator watching a create/stop/start/restart take up to a full resyncInterval (default 30s) to visibly do anything. Reconcile's own idempotent, level-triggered contract is what makes an extra, unscheduled pass always safe.

type RegistryAuthTester ¶

type RegistryAuthTester interface {
	TestRegistryAuth(ctx context.Context, host, username, password string) error
}

RegistryAuthTester is the surface POST /api/v1/registry-credentials/{id}/test needs: ask the control plane's own local Docker daemon to authenticate against a registry host with a username/password pair, without pulling anything. Fixed to this control plane's own local daemon, the same DockerPinger/ImageLister/ DockerDiskUsager/DockerPruner reasoning above applies: adding this to docker.Runtime would ripple into 6+ fakes for one narrowly-scoped signal. *docker.Client satisfies this structurally via TestRegistryAuth (internal/docker/client.go).

type RegistryCatalogClient ¶ added in v0.2.0

type RegistryCatalogClient interface {
	ListRepositories(ctx context.Context, baseURL, username, password string) ([]string, error)
	ListTags(ctx context.Context, baseURL, username, password, repository string) ([]string, error)
}

RegistryCatalogClient is the surface GET /api/v1/registry/repositories, GET /api/v1/registry/tags, and the registry-credential browse routes below need: query a Docker Registry HTTP API v2 server's catalog (_catalog, <name>/tags/list) over Basic Auth. The same client serves both Levelrail's own built-in registry (baseURL fixed to its loopback address) and an operator's stored external credential (baseURL taken from that credential's RegistryHost), since the protocol is identical either way. *registrycatalog.Client satisfies this structurally.

type RegistryCatalogSecrets ¶ added in v0.2.0

type RegistryCatalogSecrets interface {
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
}

RegistryCatalogSecrets is the surface the catalog handlers need from internal/secrets.Manager: resolve the built-in registry's generated password to authenticate the catalog query server-side. Never echoed back to the caller, the same boundary RegistryCredentialSecretsSetter.Resolve already draws for handleTestRegistryCredential.

type RegistryCredentialSecretsSetter ¶

type RegistryCredentialSecretsSetter interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
}

RegistryCredentialSecretsSetter is the surface registry credential create/update/test/browse needs from internal/secrets.Manager. Resolve is used by handleTestRegistryCredential and the two credential browse handlers in registry_catalog.go, to authenticate against the real registry on the operator's behalf; it is never used to echo a password back in an HTTP response, the same boundary GitSourceSecrets already draws for its own Resolve use.

type RegistryCredentialStore ¶

type RegistryCredentialStore interface {
	SaveRegistryCredential(ctx context.Context, c store.RegistryCredential) error
	GetRegistryCredential(ctx context.Context, id string) (store.RegistryCredential, error)
	GetRegistryCredentialByName(ctx context.Context, name string) (store.RegistryCredential, error)
	ListRegistryCredentials(ctx context.Context) ([]store.RegistryCredential, error)
	UpdateRegistryCredential(ctx context.Context, id, name, registryHost, username string, expiresAt *time.Time) error
	DeleteRegistryCredential(ctx context.Context, id string) error
}

RegistryCredentialStore is the store surface the registry credential handlers need.

type RegistrySecrets ¶

type RegistrySecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

RegistrySecrets is the surface the built-in registry settings handlers need from internal/secrets.Manager: set the generated password (first enable), check whether one exists, and clear it (disable). *secrets.Manager satisfies this structurally, the same "narrow, consumer-defined interface" shape CloudflareTunnelSecrets already establishes.

type RegistryStore ¶

type RegistryStore interface {
	GetRegistrySettings(ctx context.Context) (store.RegistrySettings, error)
	UpdateRegistrySettings(ctx context.Context, s store.RegistrySettings) error
}

RegistryStore is the store surface GET/PUT/DELETE /api/v1/settings/registry need: the single platform-wide row, always present, the same shape CloudflareTunnelStore has for its own row.

type RestoreHistoryStore ¶

type RestoreHistoryStore interface {
	ListRestoreHistory(ctx context.Context, databaseName string) ([]store.RestoreHistory, error)
}

RestoreHistoryStore is the store surface the restore history handler needs.

type RestoreRunner ¶

type RestoreRunner interface {
	RunRestore(ctx context.Context, historyID, databaseName, backupHistoryID, engine, containerName string) error
}

RestoreRunner is the surface the restore trigger handler needs from internal/backup.RestoreRunner: run one restore attempt end to end, from an already-minted history ID through to a finished store.RestoreHistory row. *backup.RestoreRunner satisfies this structurally; internal/api never imports internal/backup directly, the same boundary BackupRunner's own doc comment describes.

type Role ¶

type Role struct {
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Abilities   []string `json:"abilities"`
	ID          string   `json:"id,omitempty"`
	Visibility  string   `json:"visibility,omitempty"`
	Builtin     bool     `json:"builtin"`
	UserCount   int      `json:"user_count"`
}

Role is a curated, named preset over the raw ability list (abilities.go): picking one sets a user's Abilities to exactly this set in one action, instead of hand-picking abilities individually. It is a convenience layer, not a second permission model: every existing ability check still only ever sees the resolved Abilities.

type RoleStore ¶ added in v0.2.0

type RoleStore interface {
	ListRoles(ctx context.Context) ([]store.Role, error)
	GetRole(ctx context.Context, id string) (store.Role, error)
	CreateRole(ctx context.Context, r store.Role) error
	UpdateRole(ctx context.Context, r store.Role) error
	DeleteRole(ctx context.Context, id string) error
	RoleUserCounts(ctx context.Context) (map[string]int, error)
	UserRoleID(ctx context.Context, userID string) (string, error)
	SetUserRole(ctx context.Context, userID, roleID string) error
	ListUserEnvironmentGrants(ctx context.Context, userID string) ([]string, error)
	SetUserEnvironmentGrants(ctx context.Context, userID string, environmentIDs []string) error
	CountRootUsersExcept(ctx context.Context, userID string) (int, error)
	CountRootUsersOutsideRole(ctx context.Context, roleID string) (int, error)
	SyncRoleAbilities(ctx context.Context, roleID string) error
	UpdateRoleAndSync(ctx context.Context, r store.Role) error
}

RoleStore is the store surface the stored-role, role-assignment and environment-grant routes need.

type Route53DNSCredentialResolver ¶ added in v0.2.0

type Route53DNSCredentialResolver interface {
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
}

Route53DNSCredentialResolver is the same shape as CloudflareDNSTokenResolver for the Route53 access key pair.

type Route53DNSSecrets ¶ added in v0.2.0

type Route53DNSSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

Route53DNSSecrets is the surface the Route53 DNS-01 settings handlers need from internal/secrets.Manager, the same shape CloudflareDNSSecrets already establishes for a distinct credential shape: an AWS IAM access key pair instead of a single API token.

type Route53DNSStore ¶ added in v0.2.0

type Route53DNSStore interface {
	GetRoute53DNSSettings(ctx context.Context) (store.Route53DNSSettings, error)
	UpdateRoute53DNSSettings(ctx context.Context, s store.Route53DNSSettings) error
}

Route53DNSStore is the store surface GET/PUT /api/v1/settings/route53-dns need, the same "single platform-wide row" shape CloudflareDNSStore already establishes for a distinct ACME DNS-01 provider.

type Router ¶

type Router struct {
	// contains filtered or unexported fields
}

Router wires every internal/api handler onto one http.Handler.

func NewRouter ¶

func NewRouter(logger *slog.Logger, b *brand.Brand, s Store, opts ...Option) *Router

NewRouter builds a Router. logger defaults to slog.Default() if nil.

func (*Router) CheckDomainStatus ¶

func (rt *Router) CheckDomainStatus(ctx context.Context, domain string) (string, error)

CheckDomainStatus exposes runDomainCheck to internal/alerting's kind=domain_health evaluator (see alerting.DomainCheckSource), the same DNS check handleCheckDomain itself runs. Uses rt.publicHost only: unlike an HTTP handler, an engine tick has no request Host header to fall back on for advertisedHost's own best-effort guess, so a domain_health rule needs APP_PUBLIC_HOST configured to evaluate meaningfully; without it every domain simply reports domainCheckStatusUnconfigured, which EvaluateDomainHealth treats as inconclusive, not unhealthy.

func (*Router) CheckImageUpdate ¶ added in v0.2.0

func (rt *Router) CheckImageUpdate(ctx context.Context, serviceName string) (string, error)

CheckImageUpdate compares serviceName's tag against its registry and redeploys when the digest moved. Skips (with a reason, not an error) apps built from source, stopped, frozen, or in a protected environment: an unattended pull must never bypass the gates a manual deploy goes through.

func (*Router) DrainDeployQueue ¶ added in v0.2.0

func (rt *Router) DrainDeployQueue(ctx context.Context)

DrainDeployQueue starts every queued deploy that is no longer blocked. It runs after each deploy finishes and on a timer, so a queue survives restarts.

func (*Router) GitSourceCloneToken ¶ added in v0.2.0

func (rt *Router) GitSourceCloneToken(ctx context.Context, name, repoURL string) string

GitSourceCloneToken resolves the clone credential for a git source's own repository for in-process callers outside this package (the pipeline engine's repo reads and builds): name's stored deploy token, else the connected GitHub App's installation token. repoURL must be the stored git source URL, never a request-supplied value, since this mints a live installation token. Empty when neither exists.

func (*Router) Handler ¶

func (rt *Router) Handler() http.Handler

Handler builds the *http.ServeMux with every route registered. Called once at startup; the returned handler is what gets wrapped in an *http.Server. Route registration is split across this file (core: brand, auth, users, oauth, tokens, apps, databases) and routes_platform.go (nodes, certs, ingress, email, domains, backups, restore, storage, integrations, audit) purely to keep each function under a readable size; there is no other meaning to the split.

func (*Router) PipelineRunEnv ¶ added in v0.2.0

func (rt *Router) PipelineRunEnv(ctx context.Context, run store.PipelineRun) map[string]string

PipelineRunEnv gives a pull request pipeline run the PREVIEW_PR_NUMBER, PREVIEW_BRANCH and PREVIEW_URL of the live preview built from its branch. Other runs, and runs with no live preview, get nothing.

func (*Router) PipelineStatusReporter ¶ added in v0.2.0

func (rt *Router) PipelineStatusReporter() pipeline.StatusReporter

PipelineStatusReporter returns the reporter that posts pipeline run state to the git forge hosting an app's repository.

func (*Router) PurgeOldAuditEntries ¶

func (rt *Router) PurgeOldAuditEntries(ctx context.Context) (int64, error)

PurgeOldAuditEntries deletes every audit_log row older than effectiveAuditLogRetention, returning how many rows were removed.

func (*Router) PurgeOldWebhookDeliveries ¶ added in v0.2.0

func (rt *Router) PurgeOldWebhookDeliveries(ctx context.Context) (int64, error)

PurgeOldWebhookDeliveries deletes every webhook_deliveries row older than effectiveWebhookDeliveryRetention, returning how many rows were removed.

func (*Router) RecordRevoked ¶ added in v0.2.0

func (rt *Router) RecordRevoked(ctx context.Context, c dbaccess.TempCredential)

RecordRevoked implements dbaccess.SweepAuditor: the sweeper's own audit row.

func (*Router) ReleaseHandlers ¶ added in v0.2.0

func (rt *Router) ReleaseHandlers() map[string]deploy.ReleaseHandler

ReleaseHandlers replays held deploys; cmd/levelrail wires them into a deploy.Releaser.

func (*Router) ResumeCutoverRuns ¶ added in v0.2.0

func (rt *Router) ResumeCutoverRuns(ctx context.Context)

ResumeCutoverRuns continues or rolls back cutover runs a restart interrupted. Call it once after the router is built.

func (*Router) RevokeTemp ¶ added in v0.2.0

func (rt *Router) RevokeTemp(ctx context.Context, name, role string) error

RevokeTemp implements dbaccess.Revoker for the expiry sweeper. A database that no longer exists has nothing left to revoke.

func (*Router) RunAuditLogSweeper ¶

func (rt *Router) RunAuditLogSweeper(ctx context.Context, interval time.Duration) error

RunAuditLogSweeper calls PurgeOldAuditEntries on interval until ctx is done, the same ticker shape RunPreviewSweeper already establishes.

func (*Router) RunDeployApprovalExpirySweep ¶ added in v0.2.0

func (rt *Router) RunDeployApprovalExpirySweep(ctx context.Context, interval time.Duration) error

RunDeployApprovalExpirySweep calls expireIfStale-style cleanup across every pending approval on interval until ctx is done, the same ticker shape RunAuditLogSweeper (audit_retention.go) already establishes. The lazy per-request expiry in expireIfStale already guarantees a stale request never proceeds; this sweep only matters for a pending row no one ever looks at again, so its own status reflects reality in the UI without waiting for a read that may never come.

func (*Router) RunDeviceLoginExpirySweeper ¶ added in v0.2.0

func (rt *Router) RunDeviceLoginExpirySweeper(ctx context.Context) error

RunDeviceLoginExpirySweeper sweeps once at start, so expiries that passed while the server was down are audited, then on every interval tick.

func (*Router) RunNodeMoves ¶ added in v0.2.0

func (rt *Router) RunNodeMoves(ctx context.Context)

RunNodeMoves advances node moves and drains until ctx is done, resuming any left running by a previous process.

func (*Router) RunPreviewSweeper ¶

func (rt *Router) RunPreviewSweeper(ctx context.Context, interval time.Duration) error

RunPreviewSweeper calls SweepStalePreviewEnvironments on interval until ctx is done, matching the shape of every other periodic loop in this codebase (backup.Scheduler.Run, scheduledtask.Scheduler.Run, alerting.Engine.Run).

func (*Router) RunStatusPageSampler ¶ added in v0.2.0

func (rt *Router) RunStatusPageSampler(ctx context.Context) error

RunStatusPageSampler probes status page components until ctx ends. It returns nil at once when the status page is not configured.

func (*Router) RunWebhookDeliverySweeper ¶ added in v0.2.0

func (rt *Router) RunWebhookDeliverySweeper(ctx context.Context, interval time.Duration) error

RunWebhookDeliverySweeper calls PurgeOldWebhookDeliveries on interval until ctx is done, the same ticker shape RunAuditLogSweeper already establishes: without it, webhook_deliveries would be the one inbound- request history table in this codebase that only ever grows.

func (*Router) SetAppTeardownOptions ¶ added in v0.2.0

func (rt *Router) SetAppTeardownOptions(opts ...application.Option)

SetAppTeardownOptions is WithAppTeardownOptions for callers that learn the instance ID only after the router is built.

func (*Router) SetControlPlaneDR ¶ added in v0.2.0

func (rt *Router) SetControlPlaneDR(dr ControlPlaneDR, material EscrowMaterialReader)

SetControlPlaneDR enables the disaster recovery routes after the router is built, for wiring that needs the router's own dependencies first.

func (*Router) SetDataExport ¶ added in v0.2.0

func (rt *Router) SetDataExport(s DataExportService)

SetDataExport enables the data export routes and the /metrics scrape endpoint after the router is built.

func (*Router) SetDatabaseUpgrader ¶ added in v0.2.0

func (rt *Router) SetDatabaseUpgrader(u DatabaseUpgrader)

SetDatabaseUpgrader is WithDatabaseUpgrader for wiring after NewRouter.

func (*Router) SetDockerGuard ¶ added in v0.2.0

func (rt *Router) SetDockerGuard(c DockerGuardController)

SetDockerGuard is WithDockerGuard for a guard booted before the router.

func (*Router) SetDoctorIngressEdge ¶ added in v0.2.0

func (rt *Router) SetDoctorIngressEdge(edge DoctorIngressEdge)

SetDoctorIngressEdge makes doctor report the ingress edge policy. Call it before the router serves requests.

func (*Router) SetForgeDeployments ¶ added in v0.2.0

func (rt *Router) SetForgeDeployments(s ForgeDeploymentStore)

SetForgeDeployments wires the store that tracks forge deployments. Unset, deployments are not reported.

func (*Router) SetLoadBalancers ¶ added in v0.2.0

func (rt *Router) SetLoadBalancers(s LoadBalancerStore, registry *loadbalancer.Registry, stats loadbalancer.StatsSource)

SetLoadBalancers enables the load balancer routes after construction, for wiring that needs the router before the reconciler exists.

func (*Router) SetLocalNodeID ¶ added in v0.2.0

func (rt *Router) SetLocalNodeID(id string)

SetLocalNodeID marks which node's row in GET /api/v1/nodes is the one HostDiskCollector/HostMemoryCollector's readings are real for: the node running the control plane process itself. A late setter, not a construction Option, because cmd/levelrail's mesh setup (the only source of this ID) runs after NewRouter is called; safe to call any time before the HTTP server starts accepting requests.

func (*Router) SetMesh ¶ added in v0.2.0

func (rt *Router) SetMesh(mesh MeshStatusProvider, rotator MeshKeyRotator)

SetMesh wires GET /api/v1/mesh and POST /api/v1/nodes/{id}/mesh/rotate-key to this node's live mesh device and coordinator. A late setter for the same reason SetLocalNodeID is: cmd/levelrail's mesh setup runs after NewRouter is called. Either argument may be nil independently (a test or an embedder that wants status but not rotation, or vice versa); both routes check their own dependency and return 501 rather than assuming the other was set too.

func (*Router) SetMeshPaths ¶ added in v0.2.0

func (rt *Router) SetMeshPaths(r meshpath.Resolver)

SetMeshPaths sets the resolver the doctor and app status use to tell whether a remote node's app can be routed over the WireGuard mesh.

func (*Router) SetMeshPeerStatus ¶ added in v0.2.0

func (rt *Router) SetMeshPeerStatus(src NodeMeshStatusSource)

SetMeshPeerStatus wires node-pair diagnostics to remote agents' mesh views.

func (*Router) SetMeshZone ¶ added in v0.2.0

func (rt *Router) SetMeshZone(zone string)

SetMeshZone mirrors SetLocalNodeID's own late-setter shape, for the identical reason: cmd/levelrail's mesh setup (the only source of this value, application.WithMeshZone's own call site) runs after NewRouter is called. Empty means mesh DNS resolution is off, the same "not configured, not broken" default application.Controller itself uses.

func (*Router) SetOIDCManager ¶ added in v0.2.0

func (rt *Router) SetOIDCManager(m OIDCJWKSProvider, issuerURL string, ratePerMinute int)

SetOIDCManager wires the OIDC signing key manager: it serves the public GET /.well-known/jwks.json document and the authenticated summary GET /api/v1/pipelines/oidc reads for the dashboard. Minting the tokens themselves is wired separately, into internal/pipeline.Config.OIDCIssuer, since that lives on the pipeline engine, not this router.

func (*Router) SetOIDCRotator ¶ added in v0.2.0

func (rt *Router) SetOIDCRotator(r OIDCKeyRotator)

SetOIDCRotator wires POST /api/v1/pipelines/oidc/rotate-key. Optional: nil (the default) means the endpoint returns 501, the same not-configured shape SetOIDCManager's own JWKS route uses.

func (*Router) SetOrphanReaper ¶ added in v0.2.0

func (rt *Router) SetOrphanReaper(r *orphans.Reaper)

SetOrphanReaper enables GET /api/v1/system/orphans and POST /api/v1/system/orphans/reap, backed by the same reaper the reconcile loop runs.

func (*Router) SetPipelineEvents ¶ added in v0.2.0

func (rt *Router) SetPipelineEvents(e PipelineEvents)

SetPipelineEvents wires git webhook deliveries to pipeline triggers.

func (*Router) SetPipelineSync ¶ added in v0.2.0

func (rt *Router) SetPipelineSync(syncer PipelineSyncer, st PipelineSyncStore)

SetPipelineSync enables repository sync of pipeline files: on a push to the tracked branch, and on demand through /pipeline-sync.

func (*Router) SetPipelines ¶ added in v0.2.0

func (rt *Router) SetPipelines(st PipelineStore, runner PipelineRunner)

SetPipelines wires the pipeline endpoints after construction, for wiring code that builds the engine after the router.

func (*Router) SetPreview ¶ added in v0.2.0

func (rt *Router) SetPreview(p PreviewService)

SetPreview enables the deploy preview routes.

func (*Router) SetProxyIntegration ¶ added in v0.2.0

func (rt *Router) SetProxyIntegration(syncer *proxyroutes.Syncer, st ProxyIntegrationStore, verifyTimeout time.Duration)

SetProxyIntegration enables the managed proxy routes API. syncer must be the same instance the reconciler uses.

func (*Router) SetStorage ¶ added in v0.2.0

func (rt *Router) SetStorage(d StorageDeps)

SetStorage enables the storage routes after construction, for wiring that needs the built Router first. Call it before the server starts serving.

func (*Router) SetSupplyChain ¶ added in v0.2.0

func (rt *Router) SetSupplyChain(s SupplyChainService)

SetSupplyChain enables the SBOM and scan routes.

func (*Router) SetWALShipStatus ¶ added in v0.2.0

func (rt *Router) SetWALShipStatus(src WALShipStatusSource)

SetWALShipStatus attaches the source "pitr status" reads shipping state from.

func (*Router) StatusHostHandler ¶ added in v0.2.0

func (rt *Router) StatusHostHandler(next http.Handler) http.Handler

StatusHostHandler serves only the public status page when a request's Host matches the configured custom domain, and 404s everything else on that host so the dashboard and API are never reachable through it. Requests for any other host pass through to next untouched.

func (*Router) StatusPageAppStatus ¶ added in v0.2.0

func (rt *Router) StatusPageAppStatus(ctx context.Context, name string) (string, error)

StatusPageAppStatus reports an app's status for the status page sampler.

func (*Router) SweepDeviceLoginExpiry ¶ added in v0.2.0

func (rt *Router) SweepDeviceLoginExpiry(ctx context.Context, now time.Time) (int, error)

SweepDeviceLoginExpiry marks every pending request past its TTL as expired and audits each exactly once, returning how many it claimed.

func (*Router) SweepOrphanPreviews ¶ added in v0.2.0

func (rt *Router) SweepOrphanPreviews(ctx context.Context) (OrphanSweepResult, error)

SweepOrphanPreviews repairs state a crash can leave behind: previews stuck in deploying (the control plane died mid-build) are marked failed, and services tagged as preview workloads that no preview row owns are removed with their containers. Owned resources are never touched.

func (*Router) SweepSignInExpiry ¶ added in v0.2.0

func (rt *Router) SweepSignInExpiry(ctx context.Context, now time.Time) error

SweepSignInExpiry expires lapsed codes and approvals, auditing each once.

func (*Router) SweepStalePreviewEnvironments ¶

func (rt *Router) SweepStalePreviewEnvironments(ctx context.Context) (swept int, err error)

SweepStalePreviewEnvironments tears down every preview environment last updated before now minus effectivePreviewTTL, reusing teardownPreviewRecord, the exact same deletion path the pull-request- closed webhook and the manual teardown route already use. One preview's teardown failure never blocks the rest, the same "one broken resource must not block others" principle every other periodic loop in this codebase follows (alerting.Engine.Tick, backup.Scheduler.Tick, scheduledtask.Scheduler.Tick).

A partial teardown (http.StatusMultiStatus) counts as handled, not an error: teardownPreviewRecord already marks the row Failed with a reason and bumps its UpdatedAt, so the next sweep won't immediately retry the same broken preview every tick.

func (*Router) SweepTokenHygiene ¶ added in v0.2.0

func (rt *Router) SweepTokenHygiene(ctx context.Context, now time.Time) error

SweepTokenHygiene runs the unused-token policy and prunes expired sign-in alert links and long-unseen browsers. System tokens (no owner) are never touched: the platform itself may depend on them.

func (*Router) TriggerScheduledDeploy ¶ added in v0.2.0

func (rt *Router) TriggerScheduledDeploy(ctx context.Context, serviceName, branch string) (string, error)

TriggerScheduledDeploy resolves serviceName's git source's latest commit on branch (no clone, resolveBranchSHA) and deploys it through the same path a webhook-triggered push uses (deployFromGitSourceAs), tagged store.DeployAttemptSourceSchedule so it shows up under ListDeployments' "schedule" trigger filter. The one difference from a real push: branch may not be gs.Branch, a scheduled redeploy can target any branch, gs is only consulted for repo_url/build config.

Satisfies internal/scheduledeploy.Trigger; called only from that package's Scheduler, never directly from an HTTP handler, so it returns a plain error rather than an (int, string) status pair.

func (*Router) WithHSTS ¶ added in v0.2.0

func (rt *Router) WithHSTS(next http.Handler) http.Handler

WithHSTS wraps the embedded dashboard handler, which the API's own header middleware never sees, so the HTML document also carries Strict-Transport-Security when it is enabled (env or settings) and the request really arrived over https.

type SSHNodeProvisionStore ¶ added in v0.2.0

type SSHNodeProvisionStore interface {
	SaveSSHNodeProvision(ctx context.Context, p store.SSHNodeProvision) error
	GetSSHNodeProvision(ctx context.Context, id string) (store.SSHNodeProvision, error)
	ListSSHNodeProvisions(ctx context.Context) ([]store.SSHNodeProvision, error)
	UpdateSSHNodeProvisionProgress(ctx context.Context, id, status, detectedOS, detectedArch, log, nodeID, failureReason string, updatedAt time.Time) error
}

SSHNodeProvisionStore is the store surface the SSH node provisioning handlers need.

type SSHProvisioner ¶ added in v0.2.0

type SSHProvisioner interface {
	Provision(ctx context.Context, creds sshprovision.Credentials, params sshprovision.InstallParams, onEvent func(sshprovision.Event)) (sshprovision.DetectedHost, error)
}

SSHProvisioner is the surface *sshprovision.Provisioner satisfies, narrowed to what this package calls: a seam for this package's own tests to inject a fake that never dials a real network, the same "consumer-defined interface" shape NodeProvisioner already establishes for the cloud path.

type ScheduledTaskRunner ¶

type ScheduledTaskRunner interface {
	Run(ctx context.Context, task store.ScheduledTask) error
}

ScheduledTaskRunner runs a job on demand. *scheduledtask.Runner satisfies it structurally, and cmd/levelrail/main.go wires that same instance into the scheduler, so a scheduled tick and "run now" share one execution path.

type ScheduledTaskStarter ¶ added in v0.2.0

type ScheduledTaskStarter interface {
	Start(ctx context.Context, task store.ScheduledTask, trigger string) (runID string, err error)
}

ScheduledTaskStarter is the richer on-demand surface: it records the run's history row synchronously and returns its ID while the run itself proceeds in the background. Preferred over ScheduledTaskRunner when the wired runner provides it.

type ScheduledTaskStore ¶

type ScheduledTaskStore interface {
	SaveScheduledTask(ctx context.Context, t store.ScheduledTask) error
	GetScheduledTask(ctx context.Context, id string) (store.ScheduledTask, error)
	ListScheduledTasksForService(ctx context.Context, serviceName string) ([]store.ScheduledTask, error)
	UpdateScheduledTask(ctx context.Context, id string, command []string, schedule string, enabled bool, concurrencyPolicy string, updatedAt time.Time) error
	UpdateScheduledTaskConfig(ctx context.Context, t store.ScheduledTask, now time.Time) error
	CountScheduledTasksForService(ctx context.Context, serviceName string) (int, error)
	DeleteScheduledTask(ctx context.Context, id string) error
	ListScheduledTaskRuns(ctx context.Context, f store.ScheduledTaskRunFilter) ([]store.ScheduledTaskRun, error)
	GetScheduledTaskRun(ctx context.Context, id string) (store.ScheduledTaskRun, error)
}

ScheduledTaskStore is the store surface the scheduled job handlers need.

type SecretBinder ¶ added in v0.2.0

type SecretBinder interface {
	BindingStatus(ctx context.Context) (secrets.BindingStatus, error)
	Rebind(ctx context.Context) (secrets.RebindResult, error)
}

SecretBinder is the surface the secrets binding routes need from internal/secrets.Manager: count legacy unbound values and rebind them.

type SecretSetter ¶

type SecretSetter interface {
	SetValueGuarded(ctx context.Context, serviceName, envKey, plaintext string, overwriteLocked bool) error
	ListKeys(ctx context.Context, serviceName string) ([]store.SecretKeyInfo, error)
	SetLocked(ctx context.Context, serviceName, envKey string, locked bool) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	// ExistsForServices is Exists batched over many service names in one
	// call, used by databases.go's handleListDatabases to show every
	// row's TLS status without an Exists call per database.
	ExistsForServices(ctx context.Context, serviceNames []string, envKey string) (map[string]bool, error)
	// Resolve decrypts and returns one secret's plaintext value, the same
	// read internal/reconcile/application already does immediately before
	// container creation. environment_clone.go's own explicit,
	// opt-in-only CopySecretValues path is the one HTTP-reachable caller
	// that ever moves a decrypted value between two secret namespaces
	// (source app/environment -> cloned app/environment) rather than
	// only ever writing a caller-supplied plaintext the way
	// SetValueGuarded's other callers do.
	Resolve(ctx context.Context, serviceName, envKey string) (string, error)
	// DeleteAll permanently removes every value stored under a
	// namespace, used by handleDetachAppIntegration to clear one
	// attached integration's field values in the same request that
	// removes its app_integrations row.
	DeleteAll(ctx context.Context, serviceName string) error
}

SecretSetter is the surface the secrets handlers need from internal/secrets.Manager: set a value (with a reversible per-key lock guard), list which keys exist, toggle a key's lock, and check whether one exists without decrypting it (Exists, used by databases.go's databaseTLSEnabled to show a database's TLS status, never to read the certificate back). Every other secret-backed feature in this file keeps using Manager's plain SetValue/Resolve directly, unaffected by this narrower interface.

type SecurityStore ¶ added in v0.2.0

type SecurityStore interface {
	GetSecurityPolicy(ctx context.Context) (store.SecurityPolicy, bool, error)
	SaveSecurityPolicy(ctx context.Context, p store.SecurityPolicy) error
	GetUserSecuritySettings(ctx context.Context, userID string) (store.UserSecuritySettings, error)
	ListUserSecuritySettings(ctx context.Context) ([]store.UserSecuritySettings, error)
	SetRequireNewDeviceApproval(ctx context.Context, userID string, on bool, now time.Time) error
	FlagUserForReset(ctx context.Context, userID, reason string, now time.Time) error
	ClearUserResetFlag(ctx context.Context, userID string, now time.Time) error
	CreateSignInAlertToken(ctx context.Context, t store.SignInAlertToken) error
	ConsumeSignInAlertToken(ctx context.Context, id string, now time.Time) (store.SignInAlertToken, error)
	PruneSignInAlertTokens(ctx context.Context, cutoff time.Time) error
	ListTokenHygieneNotices(ctx context.Context) ([]store.TokenHygieneNotice, error)
	SaveTokenHygieneNotice(ctx context.Context, n store.TokenHygieneNotice) error
	MarkTokenHygieneDisabled(ctx context.Context, tokenID string, now time.Time) error
	DeleteTokenHygieneNotice(ctx context.Context, tokenID string) error
	RememberBrowser(ctx context.Context, userID, fingerprint string, now time.Time) (bool, bool, error)
	ForgetBrowser(ctx context.Context, userID, fingerprint string) error
	PruneKnownBrowsers(ctx context.Context, cutoff time.Time) error
}

SecurityStore is the store surface the security center needs.

type SelfUpgradeLauncher ¶ added in v0.2.0

type SelfUpgradeLauncher func(ctx context.Context, args []string) error

SelfUpgradeLauncher starts the host command. Tests replace it.

type SelfUpgradeStore ¶ added in v0.2.0

type SelfUpgradeStore interface {
	UpsertSelfUpgradeAttempt(ctx context.Context, a store.SelfUpgradeAttempt) error
	ListSelfUpgradeAttempts(ctx context.Context, limit int) ([]store.SelfUpgradeAttempt, error)
}

SelfUpgradeStore is what the self-upgrade routes persist and read.

type ServiceVolumeBackupHistoryStore ¶

type ServiceVolumeBackupHistoryStore interface {
	ListServiceVolumeBackupHistory(ctx context.Context, serviceName, volumeName string, limit int, before *time.Time) ([]store.BackupHistory, error)
}

ServiceVolumeBackupHistoryStore is the store surface the volume backup history handler needs, the volume counterpart of BackupHistoryStore (backups.go): GetBackupHistory is already generic by ID (shared with the database path via rt.backupHistory), so only the listing method needs its own volume-scoped query.

type ServiceVolumeBackupRunner ¶

type ServiceVolumeBackupRunner interface {
	RunVolumeBackup(ctx context.Context, historyID, serviceName, volumeName, dockerVolumeName, targetID, sqlitePath string) error
}

ServiceVolumeBackupRunner is the surface the volume backup trigger handler needs from internal/backup.Runner: the volume counterpart of BackupRunner (backups.go). *backup.Runner satisfies this structurally, the same boundary BackupRunner's own doc comment describes.

type ServiceVolumeBackupScheduleStore ¶

type ServiceVolumeBackupScheduleStore interface {
	SetServiceVolumeBackupSchedule(ctx context.Context, serviceName, volumeName, targetID, schedule string, retain, retainDays int, sqlitePath string) error
	GetServiceVolumeBackupSchedule(ctx context.Context, serviceName, volumeName string) (store.ServiceVolumeBackupConfig, error)
}

ServiceVolumeBackupScheduleStore is the store surface the volume backup schedule handlers need: the volume counterpart of SetDatabaseBackupSchedule/GetDatabaseBackupSchedule-shaped access, kept as its own table (service_volume_backups) rather than columns on desired_services, see migrations/0075's own doc comment.

type ServiceVolumeRestoreHistoryStore ¶

type ServiceVolumeRestoreHistoryStore interface {
	ListServiceVolumeRestoreHistory(ctx context.Context, serviceName, volumeName string) ([]store.RestoreHistory, error)
}

ServiceVolumeRestoreHistoryStore is the store surface the volume restore history handler needs, the volume counterpart of RestoreHistoryStore (restore.go).

type ServiceVolumeRestoreRunner ¶

type ServiceVolumeRestoreRunner interface {
	RunVolumeRestore(ctx context.Context, historyID, serviceName, volumeName, dockerVolumeName, backupHistoryID string) error
}

ServiceVolumeRestoreRunner is the surface the volume restore trigger handler needs from internal/backup.RestoreRunner: the volume counterpart of RestoreRunner (restore.go). *backup.RestoreRunner satisfies this structurally, the same boundary RestoreRunner's own doc comment describes.

type SessionLinkTokenStore ¶ added in v0.2.0

type SessionLinkTokenStore interface {
	SaveSessionLinkToken(ctx context.Context, t store.SessionLinkToken) error
	GetSessionLinkTokenByHash(ctx context.Context, hash string) (*store.SessionLinkToken, error)
	ClaimSessionLinkToken(ctx context.Context, id string) error
}

SessionLinkTokenStore is the store surface the session-link flow needs: always set, part of the core Store interface, same shape as PasswordResetTokenStore above.

type StaleSecretCounter ¶ added in v0.2.0

type StaleSecretCounter interface {
	CountStaleSecrets(ctx context.Context, cutoff time.Time) (int, error)
}

StaleSecretCounter is the surface GET /api/v1/system/doctor's stale_secrets check needs: count every secret-backed value (per-app and every secret-marked shared-env row alike) last set or rotated before cutoff. *store.DB satisfies this via CountStaleSecrets (internal/store/secret_rotation.go).

type Statement ¶

type Statement struct {
	Effect   Effect   `json:"Effect"`
	Action   []string `json:"Action"`
	Resource []string `json:"Resource"`
}

Statement is one Allow/Deny rule inside a Document: Action holds ability strings (abilities.go's AbilityRead etc.) or "*", Resource holds resource identifiers ("app:myapp", "database:main") or "*". Both are lists so one statement can cover several actions or resources at once, matching how a real IAM statement reads.

type StaticSiteStore ¶

type StaticSiteStore interface {
	ListStaticSites(ctx context.Context) ([]store.StaticSite, error)
}

StaticSiteStore is the store surface handleListStaticSites needs. *store.DB satisfies this structurally, the same consumer-defined interface convention every other Store sub-interface in this package follows (see CertStore, DatabaseStore).

type StatusPageDB ¶ added in v0.2.0

type StatusPageDB interface {
	StatusPageStore
	statuspage.Store
}

StatusPageDB is everything the status page needs from the database.

type StatusPageStore ¶ added in v0.2.0

type StatusPageStore interface {
	GetStatusPageSettings(ctx context.Context) (store.StatusPageSettings, error)
	SaveStatusPageSettings(ctx context.Context, s store.StatusPageSettings) error
	ListStatusComponents(ctx context.Context) ([]store.StatusComponent, error)
	GetStatusComponent(ctx context.Context, id string) (store.StatusComponent, error)
	SaveStatusComponent(ctx context.Context, c store.StatusComponent) error
	DeleteStatusComponent(ctx context.Context, id string) error
	SaveStatusIncident(ctx context.Context, i store.StatusIncident) error
	GetStatusIncident(ctx context.Context, id string) (store.StatusIncident, error)
	ListStatusIncidents(ctx context.Context, limit int) ([]store.StatusIncident, error)
	DeleteStatusIncident(ctx context.Context, id string) error
	AddStatusIncidentUpdate(ctx context.Context, u store.StatusIncidentUpdate) error
	ListStatusIncidentUpdates(ctx context.Context) ([]store.StatusIncidentUpdate, error)
}

StatusPageStore is the persistence surface of the status page handlers. *store.DB satisfies it.

type StatusPageViewer ¶ added in v0.2.0

type StatusPageViewer interface {
	View(ctx context.Context) (statuspage.View, error)
	PreviewView(ctx context.Context) (statuspage.View, error)
	Enabled(ctx context.Context) (bool, error)
	Invalidate()
}

StatusPageViewer builds the public view. *statuspage.Service satisfies it.

type StorageDeps ¶ added in v0.2.0

type StorageDeps struct {
	Options     StorageOptionsStore
	Archive     LogArchiveStore
	Clients     ObjectClientResolver
	Archiver    LogArchiver
	ArchiveRoot string

	// BuildCache and BuildCacheSettings enable /api/v1/build-cache; nil answers 501.
	BuildCache         BuildCacheService
	BuildCacheSettings BuildCacheSettingsStore
}

StorageDeps groups what the storage destination and log archive routes need.

type StorageOptionsStore ¶ added in v0.2.0

type StorageOptionsStore interface {
	SaveStorageOptions(ctx context.Context, o store.StorageOptions) error
	GetStorageOptions(ctx context.Context, targetID string) (store.StorageOptions, bool, error)
	ListStorageOptions(ctx context.Context) (map[string]store.StorageOptions, error)
}

StorageOptionsStore persists per-destination options.

type Store ¶

type Store interface {
	AppStore
	AppGroupLister
	AppComposeStore
	CustomTemplateStore
	DeployStore
	DeployAttemptStore
	ProbeAttemptStore
	DatabaseStore
	AuthStore
	TokenStore
	NodeStore
	CertStore
	StaticSiteStore
	BackupTargetStore
	RegistryCredentialStore
	NetworkShareStore
	FirewallRuleStore
	AppStreamStore
	BackupHistoryStore
	BackupVerificationStore
	RestoreHistoryStore
	BaseBackupHistoryStore
	PITRRestoreHistoryStore
	MajorUpgradeStore
	ServiceVolumeBackupHistoryStore
	ServiceVolumeBackupScheduleStore
	ServiceVolumeRestoreHistoryStore
	CloneRestoreHistoryStore
	VolumeCloneRestoreHistoryStore
	AppVolumeMoveStore
	ProjectStore
	OrganizationStore
	EnvironmentStore
	IngressSettingsStore
	UpdateSettingsStore
	DomainStore
	DomainBasicAuthStore
	DomainMaintenanceStore
	DomainTLSCertStore
	DomainWAFStore
	DomainRedirectStore
	DomainErrorPagesStore
	DomainSearchVisibilityStore
	DomainTrafficPolicyStore
	GitSourceStore
	AppScheduleStore
	ImageAutoUpdateStore
	AppSleepStore
	DataImportStore
	MigrationHubStore
	AppImportStore
	LoginCodeStore
	SecurityStore
	ExternalDatabaseStore
	CanaryStore
	DatabaseQueryStore
	PreviewEnvironmentStore
	GitHubAppStore
	GitLabAppStore
	BitbucketAppStore
	GiteaAppStore
	OAuthSettingsStore
	OAuthIdentityStore
	EmailSettingsStore
	ObservabilityStore
	CloudflareTunnelStore
	RegistryStore
	CloudflareDNSStore
	Route53DNSStore
	NodeProvisionStore
	SSHNodeProvisionStore
	VaultSettingsStore
	SessionLinkTokenStore
	InviteStore
	PushSubscriptions
	AuditStore
	ScheduledTaskStore
	FeatureFlagStore
	TagStore
	AppIntegrationStore
	OnboardingStore
	WebhookDeliveryStore
	PolicyStore
	HookRunStore
	DeployApprovalStore
	AIAssistantSettingsStore
	AIChatStore
	RoleStore
	AIControlStore
}

Store is the full surface NewRouter needs. *store.DB satisfies it structurally, same pattern internal/reconcile/application.ServiceStore already established for this codebase.

type SupplyChainService ¶ added in v0.2.0

type SupplyChainService interface {
	Config() supplychain.Config
	Settings(ctx context.Context, app string) (supplychain.Settings, error)
	SaveSettings(ctx context.Context, app string, patch supplychain.SettingsPatch) (supplychain.Settings, error)
	ArmOverride(ctx context.Context, app, reason string) (supplychain.Settings, error)
	Get(ctx context.Context, app, attemptID string) (supplychain.Record, error)
	OpenSBOM(ctx context.Context, app, attemptID string) ([]byte, supplychain.Record, error)
	ScanNow(ctx context.Context, app, attemptID string) (supplychain.Record, error)
	Lookup(ctx context.Context, ids []string) (map[string]supplychain.Record, error)
	DeleteApp(ctx context.Context, app string) error
}

SupplyChainService is the SBOM and scan surface; *supplychain.Service satisfies it.

type TagStore ¶ added in v0.2.0

type TagStore interface {
	SaveTag(ctx context.Context, t store.Tag) error
	GetTag(ctx context.Context, id string) (store.Tag, error)
	GetTagByName(ctx context.Context, name string) (store.Tag, error)
	ListTags(ctx context.Context) ([]store.Tag, error)
	DeleteTag(ctx context.Context, id string) error
	AttachAppTag(ctx context.Context, tagID, appName string) error
	DetachAppTag(ctx context.Context, tagID, appName string) error
	ListTagsForApp(ctx context.Context, appName string) ([]store.Tag, error)
	ListTagsForApps(ctx context.Context, appNames []string) (map[string][]store.Tag, error)
	ListAppNamesByTag(ctx context.Context, tagID string) ([]string, error)
}

TagStore is the store surface the tag handlers need, mirroring FeatureFlagStore's own shape for a different child resource.

type TelemetryQuerier ¶

type TelemetryQuerier interface {
	QueryMetrics(ctx context.Context, resourceID, metric string, from, to time.Time) ([]telemetry.Sample, error)
	QueryLogs(ctx context.Context, resourceID string, from, to time.Time, query string) ([]telemetry.LogEntry, error)
	// LatestByMetric backs handleAppResourceUsage
	// (app_resource_usage.go): the dashboard-wide "what is every app
	// doing right now" ranking, one query per metric rather than one
	// query per app.
	LatestByMetric(ctx context.Context, metric string) ([]telemetry.Sample, error)
}

TelemetryQuerier is the surface the metrics and logs query handlers need. *telemetry.Federator satisfies this structurally: today it fans out to exactly one source (this node's own local store), the same single-node-now shape already established for the reconcile agent transport, so Phase 3's real multi-node federation slots in here without this package changing.

type TokenStore ¶

type TokenStore interface {
	SaveAPIToken(ctx context.Context, t store.APIToken) error
	GetAPITokenByHash(ctx context.Context, hash string) (*store.APIToken, error)
	GetAPITokenByID(ctx context.Context, id string) (*store.APIToken, error)
	ListAPITokens(ctx context.Context) ([]store.APIToken, error)
	RevokeAPIToken(ctx context.Context, id string) error
	TouchAPITokenLastUsed(ctx context.Context, id string) error
}

TokenStore is the store surface the API-token handlers and the ability-aware auth middleware need.

type UpdateSettingsStore ¶ added in v0.2.0

type UpdateSettingsStore interface {
	GetUpdateSettings(ctx context.Context) (store.UpdateSettings, error)
	UpdateUpdateSettings(ctx context.Context, s store.UpdateSettings) error
}

UpdateSettingsStore is the store surface the update settings handlers need. *store.DB satisfies this structurally, the same consumer-defined interface convention IngressSettingsStore already establishes.

type UpgradeHistoryStore ¶ added in v0.2.0

type UpgradeHistoryStore interface {
	ListUpgradeHistory(ctx context.Context, limit int) ([]store.UpgradeHistoryEntry, error)
	AcknowledgeUpgrade(ctx context.Context, id, byType, byID, byName string, now time.Time) (e store.UpgradeHistoryEntry, changed bool, err error)
	ListNodeAgentVersionChanges(ctx context.Context, limit int) ([]store.NodeAgentVersionChange, error)
}

UpgradeHistoryStore is what the upgrade history routes need. ListUnacknowledgedUpgrades is the query the attention center consumes.

type UserCounter ¶ added in v0.2.0

type UserCounter interface {
	CountUsers(ctx context.Context) (int, error)
}

UserCounter is the store surface EnsureSetupToken needs.

type VaultSecrets ¶ added in v0.2.0

type VaultSecrets interface {
	SetValue(ctx context.Context, serviceName, envKey, plaintext string) error
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
	DeleteAll(ctx context.Context, serviceName string) error
}

VaultSecrets is the surface the Vault settings handlers need from internal/secrets.Manager: set the credential (PUT), check whether one exists (GET's has_credential, and PUT's "was a credential already set" check for the omitted-credential case), and clear it (DELETE). *secrets.Manager satisfies this structurally, the same "narrow, consumer-defined interface" shape CloudflareTunnelSecrets already establishes.

type VaultSettingsStore ¶ added in v0.2.0

type VaultSettingsStore interface {
	GetVaultSettings(ctx context.Context) (store.VaultSettings, error)
	UpdateVaultSettings(ctx context.Context, s store.VaultSettings) error
}

VaultSettingsStore is the store surface GET/PUT/DELETE /api/v1/settings/vault need: the single platform-wide row, always present, the same shape CloudflareTunnelStore has for its own row.

type VolumeCloneRestoreHistoryStore ¶

type VolumeCloneRestoreHistoryStore interface {
	ListVolumeCloneRestores(ctx context.Context, serviceName, volumeName string) ([]store.VolumeCloneRestore, error)
}

VolumeCloneRestoreHistoryStore is the store surface the volume clone-restore history handler needs, the app service volume counterpart of CloneRestoreHistoryStore (database_clone_restore.go).

type VolumeCloneRestoreRunner ¶

type VolumeCloneRestoreRunner interface {
	RunVolumeCloneRestore(ctx context.Context, historyID, sourceServiceName, sourceVolumeName, newVolumeName, backupHistoryID string) error
}

VolumeCloneRestoreRunner is the surface the volume clone-restore trigger handler needs from internal/backup.VolumeCloneRestoreRunner, the app service volume counterpart of CloneRestoreRunner. *backup. VolumeCloneRestoreRunner satisfies this structurally, the same boundary CloneRestoreRunner's own doc comment describes.

type WALShipStatusSource ¶ added in v0.2.0

type WALShipStatusSource interface {
	WALShipStatus(databaseName string) (backup.WALShipStatus, bool)
}

WALShipStatusSource reports the last WAL shipping outcome per database.

type WebhookDeliveryStore ¶

type WebhookDeliveryStore interface {
	SaveWebhookDelivery(ctx context.Context, d store.WebhookDelivery) error
	GetWebhookDelivery(ctx context.Context, id string) (*store.WebhookDelivery, error)
	ListWebhookDeliveries(ctx context.Context, serviceName string, limit int, before *time.Time) ([]store.WebhookDelivery, error)
	// DeleteWebhookDeliveriesOlderThan backs the retention sweep
	// (webhook_delivery_retention.go), the same age-based deletion
	// AuditStore.DeleteAuditEntriesOlderThan already provides for audit_log.
	DeleteWebhookDeliveriesOlderThan(ctx context.Context, cutoff time.Time) (int64, error)
}

WebhookDeliveryStore is the store surface real inbound webhook delivery history needs: row-per-delivery CRUD backing GET /api/v1/apps/{name}/webhook-deliveries and its replay endpoint. *store.DB satisfies this structurally.

Source Files ¶

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL