server

package
v0.0.0-...-76fe550 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 98 Imported by: 0

Documentation

Overview

internal/controlplane/server/clients_uow_adapter.go

clientsUoWAdapter bridges uow.UnitOfWork → clients.ServiceUoW.

uow.RepoSet satisfies clients.ClientsRepoSet structurally (both expose Clients() and Discovered()), but Go's type system requires an explicit adapter because the callback signatures differ:

uow.UnitOfWork.Do(ctx, func(uow.RepoSet) error)
clients.ServiceUoW.Do(ctx, func(clients.ClientsRepoSet) error)

The adapter wraps the concrete uow.UnitOfWork and converts the inner callback so the server/bootstrap layer can hand clients.NewService a clients.ServiceUoW without importing the uow package from the clients package (which would create an import cycle).

Index

Constants

View Source
const (

	// PanelRuntimeSourceLegacy reports that runtime values come from the
	// flag/env-based startup path (no config.toml). NOT dead code: it is
	// the live default for flag-driven deployments — see
	// cmd/control-plane/serve.go (ConfigSource wiring) and
	// defaultPanelRuntime below. "Legacy" only contrasts it with the
	// config-file path. (Audit 2026-06-09 B7: verified load-bearing.)
	PanelRuntimeSourceLegacy = "legacy"
	// PanelRuntimeSourceConfigFile reports that runtime values come from config.toml.
	PanelRuntimeSourceConfigFile = "config_file"
)
View Source
const EnvAllowDirectExposure = "PANVEX_ALLOW_DIRECT_EXPOSURE"

EnvAllowDirectExposure opts a production boot out of the trusted-proxy hard-fail (ErrTrustedProxyMisconfiguredProd) for a deployment topology that has no reverse proxy at all: the panel is bound directly to a public address and every TCP peer IS the real client. In that topology an empty TrustedProxyCIDRs is architecturally correct — resolveTrustedClientIP falls back to RemoteAddr and no collapse bug occurs — so the hard-fail added for the "proxy present, CIDRs forgotten" misconfiguration would wrongly refuse to boot a legitimate direct-exposure deployment. Setting PANVEX_ALLOW_DIRECT_EXPOSURE=1 (or "true") is the operator's explicit declaration of that topology. Mirrors the PANVEX_ALLOW_INSECURE_DB escape-hatch idiom in controlplane/config/storage.go, including that it is read directly (no plumbing through Options) since it is an environment-level operator declaration, not per-instance configuration.

View Source
const EnvAllowPlaintextCA = "PANVEX_ALLOW_PLAINTEXT_CA"

EnvAllowPlaintextCA is a belt-and-suspenders escape hatch for certificateAuthority.record persisting the CA private key without encryption. The PRIMARY guard is settings.LoadBootstrap, which fatally rejects an empty/unset PANVEX_ENCRYPTION_KEY on the only production entrypoint (`serve`) — so this branch is normally unreachable in production. This second guard exists so a future non-`serve` entrypoint (or a test/dev fixture) cannot silently write a plaintext CA private key to disk/DB; it must opt in explicitly. Mirrors the PANVEX_ALLOW_DIRECT_EXPOSURE env-var idiom (strconv.ParseBool, default false, "1"/"true" truthy) in controlplane/server/trusted_proxy.go.

View Source
const EnvForceSecureCookie = "PANVEX_FORCE_SECURE_COOKIE"

EnvForceSecureCookie unconditionally marks the session cookie Secure, regardless of TLS heuristics (Q3.U-S-13). Operators set this in any production deployment fronted by HTTPS so a misconfigured proxy or a missing X-Forwarded-Proto cannot leak the cookie over plain HTTP.

View Source
const EnvWSDevLoopback = "PANVEX_WS_DEV_LOOPBACK"

EnvWSDevLoopback opts into the development-only behaviour that allows WebSocket upgrade requests from any port on 127.0.0.1/::1/localhost. Without this env set we fall back to the strict policy: the Origin must match the exact request Host, even for loopback clients. The Vite dev server proxies /api to :8080 with Origin matching :5173, so developers must set PANVEX_WS_DEV_LOOPBACK=1 explicitly when running split-port.

View Source
const (

	// PanelClientCN is the protocol-fixed CommonName of the control-plane's
	// outbound client certificate. Listen-mode agents verify the dialing peer's
	// leaf CN against this value; it is not operator-configurable.
	PanelClientCN = "control-plane.panvex.internal"
)

Variables

View Source
var ErrAlreadyAdopted = errors.New("client already adopted")

ErrAlreadyAdopted is returned by adoptDiscoveredClient when the discovered record has already been adopted. Previously this was a generic fmt.Errorf("client already adopted"); the sentinel lets tests and HTTP handlers detect the condition reliably via errors.Is (P2-LOG-03 / L-11).

View Source
var ErrPlaintextCAPersistDenied = errors.New("refusing to persist CA private key in plaintext: no encryption key configured; set PANVEX_ENCRYPTION_KEY, or PANVEX_ALLOW_PLAINTEXT_CA=1 to explicitly allow plaintext persistence (dev/test only)")

ErrPlaintextCAPersistDenied is returned by certificateAuthority.record when no encryption key is configured and PANVEX_ALLOW_PLAINTEXT_CA has not been explicitly set.

View Source
var ErrTrustedProxyMisconfiguredProd = errors.New("panel is bound to a public address with no trusted_proxy_cidrs configured; PANVEX_ENV=production requires either PANVEX_TRUSTED_PROXY_CIDRS to be set (if behind a reverse proxy) or PANVEX_ALLOW_DIRECT_EXPOSURE=1 (if the panel is directly internet-facing with no reverse proxy)")

ErrTrustedProxyMisconfiguredProd reports that the panel is bound to a public (non-loopback) HTTP address with no TrustedProxyCIDRs configured while PANVEX_ENV=production. Left unfixed, X-Forwarded-For is silently ignored and every request — from the login handler's IP lockout, the generic rate limiter, and the IP whitelist middleware alike — resolves to the reverse proxy's own address. That collapses every client into a single shared bucket: a fleet-wide lockout (one attacker can lock out every legitimate operator) and a bypass of per-attacker throttling (an attacker's own requests average in with everyone else's). Mirrors the ErrInsecure*Prod fail-loud pattern in controlplane/config/storage.go — production must not silently start with a broken security boundary.

This only fires for the "reverse proxy present but CIDRs never configured" misconfiguration. A deployment with no reverse proxy at all (the panel is directly internet-facing) should set PANVEX_ALLOW_DIRECT_EXPOSURE=1 instead of trusted-proxy CIDRs — see checkTrustedProxyMisconfigured and EnvAllowDirectExposure.

Functions

func AtomicReplaceBinary

func AtomicReplaceBinary(currentPath, newPath string) error

AtomicReplaceBinary replaces the binary at currentPath with the one at newPath. See updates.AtomicReplaceBinary for details.

func CompareVersions

func CompareVersions(a, b string) int

CompareVersions delegates to updates.CompareVersions.

func DownloadArchive

func DownloadArchive(ctx context.Context, url, token string) (string, error)

DownloadArchive fetches a .tar.gz archive from url. See updates.DownloadArchive for details.

func DownloadChecksum

func DownloadChecksum(ctx context.Context, url, token string) (string, error)

DownloadChecksum fetches a .sha256 checksum file and returns the hex digest. See updates.DownloadChecksum for details.

func ExtractBinaryFromArchive

func ExtractBinaryFromArchive(archivePath string) (string, error)

ExtractBinaryFromArchive extracts the first file from a .tar.gz archive into a temporary executable. See updates.ExtractBinaryFromArchive for details.

func InstallScriptGitHubURL

func InstallScriptGitHubURL() string

InstallScriptGitHubURL returns the GitHub-raw URL operators should curl when the panel is unreachable from the agent host (typical for outbound bootstrap, where the panel is firewalled, and for the very first agent on a fresh control-plane). PANVEX_INSTALL_SCRIPT_GITHUB_URL allows operators to point at a fork or private mirror.

Trim whitespace so an env value with a trailing newline (common when rendered from K8s ConfigMap values) does not produce a broken URL.

func InstallScriptSHA256

func InstallScriptSHA256() string

InstallScriptSHA256 is the exported accessor for the install-script digest. cmd/control-plane wires it into bootstrap.InstallCommandConfig.ScriptHash so the generated curl|bash one-liner pins the body the panel currently serves. On the unreachable error path it returns "" — callers should treat that as "verification disabled" (consistent with the empty ScriptHash contract on InstallCommandConfig). (S-3.)

func NewSlogContextHandler

func NewSlogContextHandler(inner slog.Handler) slog.Handler

NewSlogContextHandler wraps an existing handler so emitted records carry request_id when present in ctx. Returns the inner handler unchanged when given nil so callers can compose unconditionally. Exported for cmd/control-plane to install at startup.

func ParseReleaseTag

func ParseReleaseTag(tag string) (component, version string, ok bool)

ParseReleaseTag delegates to updates.ParseReleaseTag.

func RequestIDStreamServerInterceptor

func RequestIDStreamServerInterceptor() grpc.StreamServerInterceptor

RequestIDStreamServerInterceptor mirrors RequestIDUnaryServerInterceptor for server-streaming RPCs.

func RequestIDUnaryServerInterceptor

func RequestIDUnaryServerInterceptor() grpc.UnaryServerInterceptor

RequestIDUnaryServerInterceptor is the exported entrypoint to the gRPC unary interceptor that extracts (or mints) an x-request-id and seeds it into both the server-internal request-ID context and the enrollment request-ID context — letting cmd/control-plane wire it into the gateway gRPC server.

func ResolveAssetURLs

func ResolveAssetURLs(release *GitHubRelease, component string) (binaryURL, checksumURL string)

ResolveAssetURLs delegates to updates.ResolveAssetURLs.

func RotateCertificateAuthority

func RotateCertificateAuthority(ctx context.Context, store storage.CertificateAuthorityStore, now time.Time, encryptionKey string) error

RotateCertificateAuthority mints a fresh CA and overwrites the stored record. Used by the `rotate-ca` CLI subcommand — the only safe, operator- acknowledged path for CA replacement. Every enrolled agent must re-enroll after.

func VerifyChecksum

func VerifyChecksum(path, expected string) error

VerifyChecksum computes the SHA256 of the file at path and compares it to the expected hex digest. See updates.VerifyChecksum for details.

Types

type Agent

type Agent = api.Agent

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type AgentRuntime

type AgentRuntime = api.AgentRuntime

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type AuditEvent

type AuditEvent = api.AuditEvent

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type BulkAdoptResult

type BulkAdoptResult struct {
	ID       string `json:"id"`
	Status   string `json:"status"`
	ClientID string `json:"client_id,omitempty"`
	Name     string `json:"name,omitempty"`
	Message  string `json:"message,omitempty"`
}

BulkAdoptResult is the per-id outcome from bulkAdoptDiscoveredClients. Status is one of: "adopted" (new client or merged into existing), "already_adopted" (resolved via a sibling earlier in the batch or by a concurrent caller), "error" (Message holds details).

type ConnectionClassCount

type ConnectionClassCount = api.ConnectionClassCount

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type FleetScopeAccess

type FleetScopeAccess struct {
	Global  bool
	Allowed map[string]struct{}
}

FleetScopeAccess captures the operator's effective fleet-group scope for one request. R-S-14 introduced this as the foundation for per-resource authorization: handlers that touch a fleet-group-scoped resource (clients, fleet groups, discovered clients, jobs targeting agents) consult the access set before reading or mutating.

Semantics:

  • Global == true → no per-group restriction; admin role always resolves to global, and an operator with no scope rows behaves the same way (single-tenant default).
  • Global == false → only fleet-group ids in Allowed are visible.

Methods on this struct are the only path callers should use; do not inspect Allowed directly so a future migration to a different scope model (regex, hierarchical) lands in one place.

func (FleetScopeAccess) Filter

func (a FleetScopeAccess) Filter(fleetGroupIDs []string) []string

NOTE(P5): test-only on the current slice, kept for the roadmap multi-tenant work — see IsAllowedAny above.

Filter returns the subset of input ids the operator can access. Useful for narrowing list responses and bulk-job target lists in one pass without per-id allocation.

func (FleetScopeAccess) IsAllowed

func (a FleetScopeAccess) IsAllowed(fleetGroupID string) bool

IsAllowed reports whether the operator can act on the given fleet group id. Global access always passes; otherwise the id must be in the explicit allow-set.

func (FleetScopeAccess) IsAllowedAny

func (a FleetScopeAccess) IsAllowedAny(fleetGroupIDs []string) bool

NOTE(P5): test-only on the current slice, but NOT dead — multi-tenant (per-operator fleet scoping) stays on the project roadmap and IsAllowedAny/Filter are its API surface (see docs/plans/2026-07-02-quality-remediation-spec.md §5.2, owner decision).

IsAllowedAny reports whether at least one of the supplied fleet group ids is in scope. Used by client-side checks where a managed client may live in multiple groups — operator access is granted as long as ONE of those groups is in scope. An empty input means "no group affiliation"; we treat that as deny-by-default for non-global scopes (the operator cannot see fleet-orphan clients).

type GitHubAsset

type GitHubAsset = updates.GitHubReleaseAsset

GitHubRelease re-exported for handlers/tests that still reference the short name through this package.

type GitHubRelease

type GitHubRelease = updates.GitHubRelease

GitHubRelease re-exported for handlers/tests that still reference the short name through this package.

func FetchLatestVersions

func FetchLatestVersions(ctx context.Context, repo, token string) (panel, agent *GitHubRelease, err error)

FetchLatestVersions delegates to updates.FetchLatestVersions.

type Instance

type Instance = api.Instance

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type Intervals

type Intervals struct {
	// JobsKeyEviction is how often the jobs service scans for
	// terminal-state idempotency keys to evict.
	JobsKeyEviction time.Duration
	// JobsKeyEvictionTTL is the age at which a terminal-state key is
	// evicted.
	JobsKeyEvictionTTL time.Duration
	// JobsAckExpiry is how often the jobs service scans for
	// acknowledged-but-result-less targets.
	JobsAckExpiry time.Duration
	// JobsAckExpiryTTL is the threshold after which an acknowledged
	// target with no result is transitioned to expired. Must match
	// the agent-side idempotency cache.
	JobsAckExpiryTTL time.Duration
	// Rollup is how often the timeseries rollup worker fires.
	Rollup time.Duration
	// MetricsPoller is the cadence for sampling derived gauges
	// (agent connected count, event-hub subscribers, job queue depth,
	// lockout count, DB pool stats).
	MetricsPoller time.Duration
}

Intervals bundles the worker / poller cadences that are inherent to the control-plane lifecycle (not tied to a single feature).

Q5.U-Q-04 introduced this struct; R-Q-04 wires it through Options and Server so operators (and tests) can override individual cadences without rebuilding the binary or threading new constants through every callsite. The defaults match the legacy package-level values.

func DefaultIntervals

func DefaultIntervals() Intervals

DefaultIntervals returns the values matching the legacy package-level constants. Tests that need a fast clock can construct an Intervals literal directly and pass it through Options.Intervals.

type MetricSnapshot

type MetricSnapshot = api.MetricSnapshot

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type Options

type Options struct {
	Now            func() time.Time
	Users          []auth.User
	Store          storage.Store
	UIFiles        fs.FS
	PanelRuntime   PanelRuntime
	RequestRestart func() error
	// TrustedProxyCIDRs lists additional CIDR ranges whose X-Forwarded-For
	// header is trusted for client-IP resolution (login lockout, rate
	// limiting, and the IP whitelist all share this one resolver — see
	// resolveTrustedClientIP in trusted_proxy.go). Loopback addresses are
	// always trusted regardless of this setting.
	//
	// WARNING: When the control-plane runs behind a non-loopback reverse
	// proxy and this list is empty, every inbound request resolves to the
	// proxy's IP for client identification. All clients then share a single
	// rate-limit/lockout bucket and will be throttled/locked-out
	// collectively. Always configure this field to include every
	// intermediate proxy/load-balancer CIDR. Under PANVEX_ENV=production
	// this misconfiguration is a hard boot failure (see
	// checkTrustedProxyMisconfigured / ErrTrustedProxyMisconfiguredProd);
	// outside production it is a startup WARNING only.
	TrustedProxyCIDRs []*net.IPNet
	// EncryptionKey, when set, encrypts the CA private key at rest using
	// AES-256-GCM. The key is derived from this passphrase via SHA-256.
	// Existing unencrypted keys are transparently migrated on next save.
	EncryptionKey string
	// Logger is the structured logger for the server. If nil, slog.Default() is used.
	Logger *slog.Logger
	// Version is the panel version string (e.g. "v1.2.3" or "dev").
	Version string
	// CommitSHA is the git commit hash baked in at build time.
	CommitSHA string
	// BuildTime is the RFC3339 build timestamp baked in at build time.
	BuildTime string
	// MetricsScrapeToken, when non-empty, enables the GET /metrics endpoint
	// and requires callers to present `Authorization: Bearer <token>` with a
	// byte-for-byte match. When empty, the /metrics route is not registered
	// at all (silent opt-in) so production deployments that never set the env
	// var cannot accidentally expose runtime telemetry.
	MetricsScrapeToken string
	// Intervals overrides the default worker / poller cadences. Zero-valued
	// fields fall back to DefaultIntervals(). Tests use this to compress
	// retention sweeps and rollup scans into milliseconds.
	Intervals Intervals
	// LoginTimingFloor sets the wall-clock minimum every login response
	// (success or failure) is padded to (R-S-19). Zero (unset) falls
	// back to the production default of 150ms. Pass any negative value
	// to disable the pad entirely — tests use this to avoid burning
	// real wall-clock seconds in the suite.
	LoginTimingFloor time.Duration
	// SQLitePath is the on-disk path of the SQLite database when the
	// SQLite driver is in use. Empty for Postgres deployments. Used by
	// the geoip subsystem to derive a default storage directory
	// (<dir(SQLitePath)>/geoip) so auto/URL-mode .mmdb files live next
	// to the DB file. PANVEX_GEOIP_DIR overrides regardless.
	SQLitePath string
	// Bootstrap is the typed snapshot of all bootstrap settings loaded at
	// startup by settings.LoadBootstrap. Stored on the Server so handlers
	// (e.g. GET /api/settings/values) can report the origin of each value.
	Bootstrap *settings.Bootstrap
	// BootstrapSources maps each setting name to its SourceInfo (env,
	// config_file, or default). Populated alongside Bootstrap.
	BootstrapSources settings.SourceMap
	// WebhookStorageFactory, when non-nil, enables the webhook outbox
	// subsystem: server.New invokes the factory with a vault-backed
	// decrypter, stores the returned Storage on the Server, builds a
	// Producer over it, and spawns a Worker goroutine attached to
	// the lifecycle context. nil disables webhooks entirely (event
	// sources call publish via a nil-safe wrapper).
	//
	// The factory pattern is here because the concrete storage
	// backend is known at the cmd/control-plane layer (where the
	// *sql.DB is opened) but the secret decrypter is built by
	// server.New itself (vault HKDF salt is loaded from
	// cp_secrets at boot). The factory closes over the *sql.DB and
	// gets the decrypter when the vault is ready.
	WebhookStorageFactory func(decrypt webhooks.SecretDecrypter) webhooks.Storage
}

Options defines the runtime dependencies used by the control-plane server.

type PanelRuntime

type PanelRuntime struct {
	HTTPListenAddress string
	HTTPRootPath      string
	AgentHTTPRootPath string
	PanelAllowedCIDRs []*net.IPNet
	GRPCListenAddress string
	TLSMode           string
	TLSCertFile       string
	TLSKeyFile        string
	RestartSupported  bool
	ConfigSource      string
	ConfigPath        string
}

PanelRuntime describes the currently applied network and restart runtime.

type PanelSettings

type PanelSettings struct {
	HTTPPublicURL      string `json:"http_public_url"`
	GRPCPublicEndpoint string `json:"grpc_public_endpoint"`
	PasswordMinLength  int32  `json:"password_min_length"`
	UpdatedAt          int64  `json:"updated_at_unix"`
}

PanelSettings stores operator-managed public access settings for the panel.

type ProvisionOutboundDeps

type ProvisionOutboundDeps struct {
	// Queries is the typed sqlc surface for the agents/transport tables.
	// Required; nil here behaves identically to deps==nil at the route
	// level (handler returns 503).
	Queries *dbsqlc.Queries
	// PanelCAPin / PanelCN replay the install-command flags the agent's
	// bootstrap subcommand consumes; mirror values used by
	// InstallCommandHandler. The install-script URL and gRPC endpoint are
	// no longer frozen here — they are resolved LIVE per request from the
	// panel settings (Plan 4) via Server.ResolveInstallScriptURL /
	// Server.ResolveAgentGRPCEndpoint.
	PanelCAPin string
	PanelCN    string
	// Now is an injectable clock so tests can pin token issuance
	// timestamps deterministically. nil → time.Now.
	Now func() time.Time
}

ProvisionOutboundDeps groups the runtime dependencies the provision- outbound handler needs beyond Server state. Wired in cmd/control-plane via Server.SetProvisionOutboundDeps so test fixtures that construct Server directly remain unaffected (nil deps make the handler 503).

The struct duplicates fields with bootstrap.InstallCommandConfig deliberately: the install-command handler stores its config inside a closed-over *bootstrap.InstallCommandHandler we cannot peek into, so the provision-outbound handler needs its own typed copy of the same values to build the curl|sudo-bash one-liner.

type RetentionSettings

type RetentionSettings struct {
	TSRawSeconds          int `json:"ts_raw_seconds"`
	TSHourlySeconds       int `json:"ts_hourly_seconds"`
	TSDCSeconds           int `json:"ts_dc_seconds"`
	IPHistorySeconds      int `json:"ip_history_seconds"`
	EventSeconds          int `json:"event_history_seconds"`
	AuditEventSeconds     int `json:"audit_event_seconds"`
	MetricSnapshotSeconds int `json:"metric_snapshot_seconds"`
	// JobsSeconds bounds how long terminal jobs (succeeded/failed/
	// expired) live in the jobs table before the rollup loop deletes
	// them via PruneTerminalJobs (Q2.U-P-02).
	JobsSeconds int `json:"jobs_seconds"`
	// WebhookOutboxSeconds bounds how long terminal webhook_outbox rows
	// (delivered or dead) are kept for operator audit before the rollup
	// loop prunes them via webhooks.Storage.PruneOutbox (C4).
	WebhookOutboxSeconds int `json:"webhook_outbox_seconds"`
	// EnrollmentTokenSeconds bounds how long dead enrollment tokens
	// (consumed, revoked, or expired-unconsumed) are kept for operator
	// forensics before the rollup loop prunes them via
	// PruneEnrollmentTokens (C4).
	EnrollmentTokenSeconds int `json:"enrollment_token_seconds"`
	// ConfigApplyBatchSeconds bounds how long terminal group config-apply
	// batches (succeeded/failed/halted) and their targets live before the
	// rollup loop deletes them via PruneConfigApplyBatches (Phase A / A5).
	// Active (running) batches are preserved so an in-flight rollout can
	// never be pruned mid-flight.
	ConfigApplyBatchSeconds int `json:"config_apply_batch_seconds"`
}

RetentionSettings controls how long timeseries data is kept before pruning.

type RuntimeDC

type RuntimeDC = api.RuntimeDC

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type RuntimeEvent

type RuntimeEvent = api.RuntimeEvent

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type RuntimeMeWritersSummary

type RuntimeMeWritersSummary = api.RuntimeMeWritersSummary

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type RuntimeSystemLoad

type RuntimeSystemLoad = api.RuntimeSystemLoad

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type RuntimeUpstream

type RuntimeUpstream = api.RuntimeUpstream

The control-plane presentation types moved to internal/controlplane/api so domain packages (agents.LiveStore) can consume them without importing server. server keeps these aliases so its existing call sites — hundreds of references to Agent / Instance / AgentRuntime / ... — compile unchanged.

type Server

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

Server wires local-auth, inventory, jobs, and operator APIs into one HTTP surface.

func New

func New(options Options) (*Server, error)

New constructs the control-plane Server. Returns an error (Plan 3 Task 4 / Q-7) when boot-time secret initialisation fails — CSRF secret load, vault HKDF salt resolution, secret-vault construction, or username log-hash key derivation. The lifecycle context created up front is cancelled before returning the error so a wedged storage call started during initSecrets does not outlive the failed constructor.

func (*Server) AppendAudit

func (s *Server) AppendAudit(ctx context.Context, actorID, action, targetID string, details map[string]any)

AppendAudit records one audit-trail entry (best-effort).

func (*Server) ApplyAgentSnapshot

func (s *Server) ApplyAgentSnapshot(ctx context.Context, snap gateway.AgentSnapshot) error

ApplyAgentSnapshot applies an agent runtime snapshot against panel state.

func (*Server) AuthorizeAgentConnect

func (s *Server) AuthorizeAgentConnect(ctx context.Context, sess agenttransport.AgentSession) (string, string, error)

AuthorizeAgentConnect runs the synchronous handshake required before the stream can stay open: identity check, in-memory revocation lookup, per-store cert serial pin, and per-agent rate limit. Returns the agent id and the cert serial it presented (so the mid-stream watcher can re-check the pin) on success.

func (*Server) CAPINHex

func (s *Server) CAPINHex() string

CAPINHex returns the lower-hex SHA-256 fingerprint of the panel's CA DER bytes. Agents that receive this value via the install command pin the panel CA against it on first connect.

func (*Server) CertificateAuthority

func (s *Server) CertificateAuthority() bootstrap.CertificateAuthority

CertificateAuthority returns the panel's CA, which implements bootstrap.CertificateAuthority (SignCSR). Used by main.go to wire the EnrollDriver for outbound-supervisor bootstrap exchanges.

func (*Server) Close

func (s *Server) Close()

Close stops background workers and drains pending writes. It should be called before closing the storage backend.

Shutdown ordering (P2-LOG-10 / M-R4 / P7-R6):

  1. batchWriter.StopWithTimeout(10s) FIRST — drains the audit-events queue (and the 7 other streams) before any background goroutine that might still be producing audits is shut down. This bounds the worst-case shutdown time at 10s so a wedged DB cannot hang the process indefinitely, while still giving the DB a realistic window to absorb in-flight rows.
  2. metrics / rollup goroutines stop afterwards.

Events enqueued AFTER this point may race with the final drain and can be dropped — upstream callers (HTTP handlers, gRPC streams) must stop before Close() runs to guarantee zero loss.

func (*Server) Context

func (s *Server) Context() context.Context

Context returns the Server's lifecycle context. The context is alive between New() and Close(); Close() cancels it so long-lived workers can abort in-flight storage calls during shutdown. Callers must NOT cache the returned context across a Close — derive child contexts via context.WithCancel/WithTimeout from the value returned here at goroutine start.

If the Server was constructed via a path that did not initialise the lifecycle context (e.g. test helpers using newServerFromOptions directly), returns context.Background() so worker code that does <-ctx.Done() does not panic on a nil receiver.

func (*Server) EffectiveGRPCListenAddress

func (s *Server) EffectiveGRPCListenAddress() string

EffectiveGRPCListenAddress mirrors EffectiveHTTPListenAddress for gRPC.

func (*Server) EffectiveHTTPListenAddress

func (s *Server) EffectiveHTTPListenAddress() string

EffectiveHTTPListenAddress returns the HTTP bind address the panel should listen on: the live store value (env-override > db-seed > registry default :8080), or the panelRuntime fallback when no store is wired (test fixtures).

On a store-backed boot, OperationalStore.Reload always populates a value for every operational field (env override, stored value, or registry default), so RawByName never returns "" for http.listen_address once Reload has run. The panelRuntime fallback only matters for the no-store path, which never binds a listener.

func (*Server) GRPCTLSConfig

func (s *Server) GRPCTLSConfig() *tls.Config

GRPCTLSConfig returns the TLS configuration used by the agent gateway listener.

func (*Server) Gateway

func (s *Server) Gateway() *gateway.Gateway

Gateway returns the agent gRPC gateway, for serve.go registration and tests that exercise the Connect/RenewCertificate/ReportEnrollmentSteps surface via the full Server.

func (*Server) HandleInStreamRenewalRequest

func (s *Server) HandleInStreamRenewalRequest(ctx context.Context, agentID string, sess agenttransport.AgentSession, req *gatewayrpc.RenewalRequest)

HandleInStreamRenewalRequest processes a cert renewal request from an agent over the existing Connect bidi-stream. The response is sent back inline; errors are reported via RenewalResponse.error so the stream stays open.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the configured HTTP handler for the control-plane API.

func (*Server) MarkTransportSwitchResolved

func (s *Server) MarkTransportSwitchResolved(agentID string)

MarkTransportSwitchResolved clears the A2 "switched but never reconnected" marker: any accepted agent stream (inbound or outbound) proves the agent is reachable in its current transport mode.

func (*Server) NotifyAgentSession

func (s *Server) NotifyAgentSession(agentID string)

NotifyAgentSession wakes the session currently attached to agentID.

func (*Server) OutboundGRPCTLSConfig

func (s *Server) OutboundGRPCTLSConfig() *tls.Config

OutboundGRPCTLSConfig returns the TLS configuration the panel uses when DIALING listen-mode agents (A1). Distinct from GRPCTLSConfig (the listener-side server config): RootCAs instead of ClientCAs, panel CLIENT certificate instead of the server certificate.

func (*Server) PanelClientCertificate

func (s *Server) PanelClientCertificate() tls.Certificate

PanelClientCertificate returns the panel's outbound client identity for the bootstrap (EnrollOutbound) dial path.

func (*Server) ReconcileDiscoveredClients

func (s *Server) ReconcileDiscoveredClients(ctx context.Context, agentID string, records []*gatewayrpc.ClientDetailRecord, telemtUnreachable bool, observedAt time.Time)

ReconcileDiscoveredClients reconciles a full client-list response.

func (*Server) RecordClientJobResult

func (s *Server) RecordClientJobResult(ctx context.Context, agentID, jobID string, success bool, message, resultJSON string, observedAt time.Time)

RecordClientJobResult updates client deployment state from a job result.

func (*Server) RecordEnrollmentSteps

RecordEnrollmentSteps ingests an agent-reported batch of enrollment timeline events for the attempt identified by attempt_id. The agent calls this once the Connect stream is up so the panel timeline gains the agent-side stages it cannot otherwise observe (cert persisted, gateway dialed, tls handshake). An empty attempt_id, a nil recorder (mock stores without DB() — see lifecycle.go), or zero events all short-circuit to a successful no-op so the agent never panics on its best-effort flush. A genuine store error surfaces back to the caller so the agent's slog.Warn captures it.

func (*Server) RegisterAgentSession

func (s *Server) RegisterAgentSession(agentID string, cancelConn context.CancelFunc) (*agents.Session, func())

RegisterAgentSession installs a new gRPC stream session for agentID.

func (*Server) RenewAgentCertificate

func (s *Server) RenewAgentCertificate(ctx context.Context, agentID string, request *gatewayrpc.RenewCertificateRequest) (*gatewayrpc.RenewCertificateResponse, error)

RenewAgentCertificate is the post-authentication core of the unary RenewCertificate RPC: revocation check, agent/request identity match, CSR issuance, in-memory cert-date update, serial persist and pin. The caller (Gateway.RenewCertificate) has already resolved agentID from the peer context.

func (*Server) ResolveAgentGRPCEndpoint

func (s *Server) ResolveAgentGRPCEndpoint(r *http.Request) string

ResolveAgentGRPCEndpoint returns the gRPC endpoint agents dial, derived from the LIVE grpc.public_endpoint panel setting (falling back to the request host). Read per request.

Exported so cmd/control-plane can pass it as a method value into bootstrap.InstallCommandConfig.PanelURLFn.

func (*Server) ResolveClientIDByName

func (s *Server) ResolveClientIDByName(agentID, clientName string) string

ResolveClientIDByName resolves a client_id from an agent-scoped name.

func (*Server) ResolveInstallScriptURL

func (s *Server) ResolveInstallScriptURL(r *http.Request) string

ResolveInstallScriptURL returns the panel-hosted install-agent.sh URL for the current request, derived from the LIVE http.public_url panel setting (falling back to the request host). Honours PANVEX_INSTALL_SCRIPT_URL via installScriptPanelURL. Read per request so a saved http.public_url change takes effect without a restart.

Exported so cmd/control-plane can pass it as a method value into bootstrap.InstallCommandConfig.ScriptURLFn.

func (*Server) RunAgentSession

func (s *Server) RunAgentSession(ctx context.Context, sess agenttransport.AgentSession, meta agenttransport.NodeMeta) error

RunAgentSession is the SessionHandler entry point invoked by agenttransport.Manager for outbound (panel-dialed) sessions. It delegates to the gateway, which runs the direction-agnostic agent protocol.

func (*Server) SetAgentTransportManager

func (s *Server) SetAgentTransportManager(m *agenttransport.Manager)

SetAgentTransportManager wires the agenttransport.Manager so the transport-mode change handler can notify it when an agent's mode is updated. Safe to call concurrently with HTTP requests. Also wires the Prometheus supervisor-gauge callback, the SPKI cert-pin reader (S-02), and the outbound backoff getters (settings Task 4) if metrics / storage are available.

func (*Server) SetInstallCommandHandler

func (s *Server) SetInstallCommandHandler(h *bootstrap.InstallCommandHandler)

SetInstallCommandHandler wires the bootstrap install-command handler. Safe to call concurrently with HTTP requests. Nil h is accepted — the route returns 503 until a non-nil handler is provided.

func (*Server) SetPprofListenerAddr

func (s *Server) SetPprofListenerAddr(addr string)

SetPprofListenerAddr opts the server into S-07's separate-listener pprof mode. When addr is non-empty, the admin-router pprof registration is skipped, and the operator is responsible for calling StartPprofListener at startup to bring up a dedicated listener bound to that address.

Recommended values are loopback-only (127.0.0.1:6060, [::1]:6060) so access requires shell on the host. Public binds defeat the entire point of the separation.

Must be called before Handler() so the route registration sees the flag.

func (*Server) SetProvisionOutboundDeps

func (s *Server) SetProvisionOutboundDeps(d *ProvisionOutboundDeps)

SetProvisionOutboundDeps wires the deps the provision-outbound handler needs. Safe to call concurrently with HTTP requests; nil deps cause the route to return 503 until the next call (mirrors SetInstallCommandHandler).

func (*Server) SetSubscriptionListener

func (s *Server) SetSubscriptionListener(addr, baseURL string)

SetSubscriptionListener configures the public /sub listener. addr is the bind address (e.g. ":8081"); baseURL is the public origin used to build shareable links in the admin UI (e.g. "https://sub.example.com").

func (*Server) SetTestBootstrap

func (s *Server) SetTestBootstrap(b *settings.Bootstrap, src settings.SourceMap)

SetTestBootstrap is for tests only; production wiring goes via the constructor in T27.

func (*Server) Settings

func (s *Server) Settings() *settings.OperationalStore

Settings returns the operational settings store.

func (*Server) ShouldTerminateForRevocation

func (s *Server) ShouldTerminateForRevocation(ctx context.Context, agentID, presentedSerial string) bool

ShouldTerminateForRevocation returns true when either the in-memory revoked set has the agent or the persisted cert pin no longer matches the presented serial.

func (*Server) StartPprofListener

func (s *Server) StartPprofListener(ctx context.Context) (net.Addr, func(context.Context) error, error)

StartPprofListener brings up the separate pprof HTTP listener on the configured address. Returns the bound listener address (useful for tests and structured logs) and a shutdown func to be invoked during graceful shutdown. Errors out if the address is unset or the bind fails.

Threat model (S-07):

  • Loopback bind: only callers with shell on the host can reach pprof. Even an admin panel user cannot pull goroutine stacks.
  • The listener is plain HTTP — no TLS — because the operator reaches it through SSH port-forward (`ssh -L 6060:localhost:6060 host`) rather than over the wire.
  • Connection-level read/write timeouts mirror the panel's HTTP server so a hung profile request cannot block shutdown.

func (*Server) StartSubscriptionListener

func (s *Server) StartSubscriptionListener(ctx context.Context) (net.Addr, func(context.Context) error, error)

StartSubscriptionListener binds and serves the public /sub listener. Mirrors StartPprofListener. Returns the bound addr and a shutdown func.

func (*Server) StartupError

func (s *Server) StartupError() error

StartupError reports the first initialization error encountered while restoring persisted state.

func (*Server) SubscriptionBaseURL

func (s *Server) SubscriptionBaseURL() string

SubscriptionBaseURL is read by the admin layer (Plan 3) to build the URL. It prefers the live dashboard setting over the env-seeded field, so the operator can change the public origin without restarting the panel.

func (*Server) WireEnrollDriver

func (s *Server) WireEnrollDriver(d *bootstrap.EnrollDriver)

WireEnrollDriver attaches the server's Prometheus counter and audit-event hooks to an EnrollDriver so its Run outcomes are recorded. Call this immediately after constructing the driver and before starting the outbound supervisor. Safe to call with a nil driver (no-op).

type UpdateSettings

type UpdateSettings = updates.Settings

UpdateSettings / UpdateState moved to internal/controlplane/updates with the updates.Service extraction (P8.2i). server keeps aliases so its call sites (panel_settings.go, http_updates.go, update_checker.go, tests) compile unchanged.

type UpdateState

type UpdateState = updates.State

UpdateSettings / UpdateState moved to internal/controlplane/updates with the updates.Service extraction (P8.2i). server keeps aliases so its call sites (panel_settings.go, http_updates.go, update_checker.go, tests) compile unchanged.

type UserAppearance

type UserAppearance struct {
	Theme     string `json:"theme"`
	Density   string `json:"density"`
	HelpMode  string `json:"help_mode"`
	UpdatedAt int64  `json:"updated_at_unix"`
}

UserAppearance stores the current user's persisted appearance preferences.

Source Files

Jump to

Keyboard shortcuts

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