Help us shape the future of Go! Take the 2026 Go Developer Survey

controlplane

package
v1.4.14 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package controlplane implements the shared control-plane core consumed by both the nSelf CLI and the nSelf Admin companion. It provides declarative multi-server inventory management, capability resolution, topology-aware deployment pipelines, and load-balancer lifecycle hooks.

Design invariants:

  • Secrets are never stored inline. SSHKeyRef holds an env-var NAME whose value is the path to the private key. Host holds user@host only.
  • Capability is derived from runtime probes, never from stored authority.
  • All file-system writes (inventory, state cache) are created mode 0600.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyServer added in v1.4.14

func ApplyServer(ctx context.Context, envName string, srv Server, keyPath string, ra *RemoteApply) error

ApplyServer applies the env's artifact set to one server: the pipeline's per-server step, and the legacy single-host deploy's.

func ComposeFilesForDeploy added in v1.4.13

func ComposeFilesForDeploy(base string) ([]string, error)

ComposeFilesForDeploy includes every generated compose fragment in its recorded merge order. A missing listed file fails closed in composeImages.

func DeployKnownHostsPath added in v1.4.13

func DeployKnownHostsPath() string

DeployKnownHostsPath is the dedicated pin file, separate from ~/.ssh/known_hosts.

func HostKeyOptions added in v1.4.13

func HostKeyOptions(ctx context.Context, env, server string, tier Tier, host string, shipping bool) ([]string, error)

HostKeyOptions selects the strict policy for secret shipping and prod hosts.

func IsProdClass added in v1.4.13

func IsProdClass(inv *Inventory, env string) bool

IsProdClass reports whether env names a production-class environment.

func Migrate

func Migrate(inv *Inventory) error

Migrate upgrades inv in-place to currentSchemaVersion. It is idempotent: calling it multiple times on the same inventory produces the same result. Currently the only supported input version is 1 (same as current); future versions will add migration steps here.

func Preflight added in v1.4.13

func Preflight(ctx context.Context, inv *Inventory, env string, composeFiles []string) error

Preflight checks all remote hosts in one environment against the compose images.

func TierRank added in v1.4.13

func TierRank(t Tier) int

TierRank orders tiers from local to production.

func TrustHostKey added in v1.4.13

func TrustHostKey(ctx context.Context, host, offered string) error

TrustHostKey rescans a host and pins only the offered live fingerprint.

func ValidateInventory added in v1.4.13

func ValidateInventory(inv *Inventory) error

ValidateInventory applies the same checks used by Load to a pending edit.

func ValidateServerName

func ValidateServerName(name string) error

ValidateServerName returns an error when name contains characters that would be dangerous in a remote SSH argument position.

func VerifiedSnapshot added in v1.4.14

func VerifiedSnapshot(ctx context.Context, r deploy.Remote, remotePath, tag string) (string, error)

VerifiedSnapshot takes and verifies a backup tagged tag on the host behind r.

func Write

func Write(projectRoot string, inv *Inventory) error

Write persists inv to .nself/control-plane.yaml inside projectRoot. The file and its parent directory are created with mode 0600 / 0700 respectively if they do not already exist.

Types

type Capability

type Capability string

Capability describes the level of control the current host can exercise over a remote server, as determined by runtime probes.

const (
	// CapManage indicates full SSH + Docker access; deploy operations proceed.
	CapManage Capability = "manage"

	// CapReadOnly indicates the server is known but not fully reachable; the
	// pipeline reports a SKIPPED line for this server.
	CapReadOnly Capability = "read-only"

	// CapHidden indicates the server should not appear in normal output
	// (e.g. Host is empty, or not relevant to the current operation).
	CapHidden Capability = "hidden"
)

type DeployResult

type DeployResult struct {
	// Servers contains one entry per server in the resolved target set.
	Servers []ServerResult

	// PrimarySkipped is true when at least one primary app server was skipped
	// due to read-only capability. Callers should return a non-zero exit code.
	PrimarySkipped bool
}

DeployResult is the top-level result of a pipeline run.

func Run

func Run(ctx context.Context, inv *Inventory, env string, prober Prober, composePath string) (*DeployResult, error)

Run executes the topology-aware pipeline (observability, app servers, then LB reload) for env only, failing closed; every remote server gets deploy.DeployViaSsh.

func RunWithApply added in v1.4.14

func RunWithApply(ctx context.Context, inv *Inventory, env string, prober Prober, composePath string, apply *RemoteApply) (*DeployResult, error)

RunWithApply is RunWithEnv for the v1.5 artifact-set apply: composePath is the scratch build's compose (Preflight reads its manifest), apply the staged set.

func RunWithEnv added in v1.4.13

func RunWithEnv(ctx context.Context, inv *Inventory, env string, prober Prober, composePath, envFile, envName string) (*DeployResult, error)

RunWithEnv is Run that also ships envFile (a resolved env snapshot) as .env.<envName> on every server; both empty means compose only.

type DrainResult

type DrainResult int

DrainResult indicates the outcome of a Drain or Enable call.

const (
	// DrainOK means the helper ran and the command succeeded.
	DrainOK DrainResult = iota

	// DrainHelperAbsent means the helper binary was not found on the LB server
	// (SSH exit 127). Callers should log a WARN and degrade gracefully.
	DrainHelperAbsent

	// DrainFailed means the helper was found but returned a non-zero exit code.
	DrainFailed
)

func Drain

func Drain(ctx context.Context, srv Server, appName string) (DrainResult, error)

Drain removes appName from the active upstream pool on the LB server by running the remote nself-lb helper:

ssh ... <remote_path>/bin/nself-lb drain <appName>

The ctx controls the lifetime of the SSH subprocess; cancelling ctx or reaching its deadline aborts the in-flight connection.

If the helper binary is absent (SSH exit 127), Drain returns DrainHelperAbsent and a nil error so the caller can degrade gracefully (log WARN + continue). Other SSH failures return DrainFailed with a descriptive error.

func Enable

func Enable(ctx context.Context, srv Server, appName string) (DrainResult, error)

Enable re-adds appName to the active upstream pool on the LB server by running the remote nself-lb helper:

ssh ... <remote_path>/bin/nself-lb enable <appName>

The ctx controls the lifetime of the SSH subprocess. Same error semantics as Drain.

type Environment

type Environment struct {
	// Name is a well-known identifier: "local", "staging", "prod", or a
	// custom label defined by the operator.
	Name string `yaml:"name" json:"name"`

	// Kind is "local" for loopback environments and "remote" for SSH-accessed
	// environments. Capability resolution uses this to short-circuit probes.
	Kind                   string `yaml:"kind" json:"kind"`
	Tier                   Tier   `yaml:"tier,omitempty" json:"tier,omitempty"`
	RequireVerifiedRelease *bool  `yaml:"require_verified_release,omitempty" json:"require_verified_release,omitempty"`

	// Servers is the ordered list of hosts in this environment.
	Servers []Server `yaml:"servers" json:"servers"`
}

Environment groups the servers that form a single deployment target. Kind distinguishes local environments (no SSH needed) from remote ones.

type Inventory

type Inventory struct {
	// SchemaVersion is incremented when the YAML structure changes in a
	// backward-incompatible way. Current supported version: 2.
	SchemaVersion int `yaml:"schema_version" json:"schema_version"`

	// Project is the canonical project name, matching the nSelf project
	// identifier (e.g. "nself", "ummat", "unity").
	Project string `yaml:"project" json:"project"`

	// Environments maps environment name to its definition. Keys must be unique
	// and match the Environment.Name field of their value.
	Environments map[string]Environment `yaml:"environments" json:"environments"`
}

Inventory is the top-level declarative model persisted in .nself/control-plane.yaml (mode 0600). SchemaVersion enables forward-compatible migrations. Secrets are never inlined here.

func Load

func Load(projectRoot string) (*Inventory, error)

Load reads the control-plane inventory for the given project root.

If .nself/control-plane.yaml exists it is unmarshalled and Migrate is applied to bring it to the current schema version. If the file is absent, Load synthesizes a single-server inventory from NSELF_DEPLOY_HOST_<TARGET> environment variables so that legacy configurations continue to work without modification (back-compat guarantee).

func ScopeToEnv added in v1.4.13

func ScopeToEnv(inv *Inventory, env string) (*Inventory, error)

ScopeToEnv returns a copy of inv holding only the environment named env. It fails closed: nil inventory, empty name, or a name that is not an exact key of inv.Environments returns an error and no inventory.

type Prober

type Prober interface {
	// SSHReachable attempts an SSH connection to s and returns (true, nil) on
	// success or (false, err) with a descriptive error on failure. The err
	// value is used to populate TargetStatus.Reason; callers must not treat
	// non-nil err as fatal — it may indicate a transient network condition.
	SSHReachable(s Server) (reachable bool, latencyMS int, err error)

	// DockerOK runs "docker info" over SSH and returns true when the Docker
	// daemon responds. It is only called when SSHReachable returns true.
	DockerOK(s Server) (bool, error)

	// KeyState checks whether the key referenced by s.SSHKeyRef exists on the
	// local filesystem. It returns the resolved key path for logging.
	// KeyState must not perform any network I/O.
	KeyState(s Server) (present bool, path string)
}

Prober is the interface through which the resolver obtains runtime facts about a server. By injecting this interface the resolver itself contains zero os/exec calls, making it fully unit-testable without network access.

Implementations must be safe for concurrent use from multiple goroutines.

type RemoteApply added in v1.4.14

type RemoteApply struct {
	Set          deploy.ArtifactSet
	Expected     deploy.TargetSnapshot // the primary's snapshot the build used
	LocalPlugins []deploy.PluginRef
	ProdClass    bool // tier prod: verified backup, stateful recreate needs confirmation
	Yes          bool
	Confirm      func(msg string) bool
	StrictHasura bool
	Clock        docker.Clock
	Out          io.Writer
	// BackupID is the one verified database backup of the env, taken on its
	// primary (BackupHost) before any server was touched; other servers never run
	// `nself backup`. Empty off tier prod.
	BackupID, BackupHost string
	// Previous are the paths the env's previous deploy shipped (local record).
	Previous []string
	// Record is called after each server's apply, whether it succeeded or not.
	Record func(srv Server, res deploy.ApplyResult, err error)
	// Open opens the remote of a server; nil means the strict-host-key ssh
	// remote. Tests substitute a recorder.
	Open func(ctx context.Context, srv Server, keyPath, env string) (deploy.Remote, error)
}

RemoteApply is the v1.5 payload for one env.

func (*RemoteApply) OpenRemote added in v1.4.14

func (ra *RemoteApply) OpenRemote(ctx context.Context, srv Server, keyPath, env string) (deploy.Remote, error)

OpenRemote opens the remote of srv through ra.Open or the ssh default.

type SSHProber

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

SSHProber is the production implementation of Prober. It performs real SSH and docker probes, enforces a global concurrency limit, maintains a per-host mutex to prevent duplicate concurrent probes to the same host, and caches results to .nself/state/capability.json (0600) for probeCacheTTL.

func NewSSHProber

func NewSSHProber(projectRoot string, refresh bool) *SSHProber

NewSSHProber constructs a production Prober. projectRoot is the working directory (parent of .nself/). When refresh is true the on-disk cache is ignored for this run.

func (*SSHProber) Architecture added in v1.4.13

func (sp *SSHProber) Architecture(ctx context.Context, s Server) (string, error)

Architecture returns the inventory architecture or one cached SSH uname -m.

func (*SSHProber) DockerOK

func (sp *SSHProber) DockerOK(s Server) (bool, error)

DockerOK runs "docker info" over SSH to verify the Docker daemon is running on s. It is only called by the resolver when SSHReachable returned true.

func (*SSHProber) KeyState

func (sp *SSHProber) KeyState(s Server) (present bool, path string)

KeyState checks whether the SSH key referenced by s.SSHKeyRef exists on the local filesystem. It does not perform any network I/O.

func (*SSHProber) SSHReachable

func (sp *SSHProber) SSHReachable(s Server) (bool, int, error)

SSHReachable performs an SSH probe to s.Host using the key from s.SSHKeyRef. The exact argument vector is:

ssh -i <key> -o BatchMode=yes -o StrictHostKeyChecking=accept-new
    -o ConnectTimeout=5 -o ForwardAgent=no <user@host> "true"

Results are cached to .nself/state/capability.json for probeCacheTTL. The global semaphore limits concurrent probes to probeConcurrencyLimit. A per-host mutex prevents duplicate probes to the same host.

type Selector added in v1.4.13

type Selector struct {
	Env    string
	Tier   Tier
	Server string
}

Selector filters inventory targets. Empty fields match all entries.

type Server

type Server struct {
	// Name is a human-readable identifier unique within the environment.
	Name string `yaml:"name" json:"name"`

	// Role classifies the server's function (app, lb, observability, db, worker).
	Role ServerRole `yaml:"role" json:"role"`

	// Host is the SSH target in user@host form. Empty string means local-only
	// (capability is always "manage" for local environments).
	Host string `yaml:"host,omitempty" json:"host,omitempty"`

	// SSHKeyRef is the name of an environment variable whose value holds the
	// absolute path to the SSH private key. Never the key path itself.
	SSHKeyRef string `yaml:"ssh_key_ref,omitempty" json:"ssh_key_ref,omitempty"`

	// RemotePath is the absolute path on the remote host where the nSelf
	// stack is installed (e.g. /opt/nself). Used to locate nself-lb helper.
	RemotePath string `yaml:"remote_path,omitempty" json:"remote_path,omitempty"`

	// Primary marks the authoritative app server. The pipeline returns a
	// non-zero exit code if this server is skipped due to read-only capability.
	Primary bool   `yaml:"primary,omitempty" json:"primary,omitempty"`
	Arch    string `yaml:"arch,omitempty" json:"arch,omitempty"`

	// Upstreams lists the names of app-server backends this LB routes to.
	// Applicable only when Role == RoleLB.
	Upstreams []string `yaml:"upstreams,omitempty" json:"upstreams,omitempty"`
}

Server describes a single remote (or local) host within an environment.

Host must be in user@host form. No key paths or passwords are stored here; SSHKeyRef holds the name of an environment variable whose value is the absolute path to the private key file.

type ServerResult

type ServerResult struct {
	// Env is the environment name.
	Env string

	// Server is the server name.
	Server string

	// Role is the server's role (used for ordering and reporting).
	Role ServerRole

	// Status is one of "ok", "skipped", "failed".
	Status string

	// Err is non-nil when Status == "failed".
	Err error

	// Primary marks whether this server is the primary app server.
	Primary bool
}

ServerResult records the outcome of deploying to one server.

type ServerRole

type ServerRole string

ServerRole classifies a server's function within an environment. The topology-aware pipeline uses roles to order operations correctly.

const (
	// RoleApp identifies a primary application server.
	RoleApp ServerRole = "app"
	// RoleLB identifies a load-balancer server.
	RoleLB ServerRole = "lb"
	// RoleObservability identifies a monitoring / observability server.
	RoleObservability ServerRole = "observability"
	// RoleDB identifies a dedicated database server.
	RoleDB ServerRole = "db"
	// RoleWorker identifies a background-worker server.
	RoleWorker ServerRole = "worker"
)

type Target added in v1.4.13

type Target struct {
	Env    string
	Tier   Tier
	Server Server
}

Target couples a server with its environment for callers of ResolveTargets.

func ResolveTargets added in v1.4.13

func ResolveTargets(inv *Inventory, sel Selector) ([]Target, error)

ResolveTargets returns matching servers sorted by environment, role, name.

type TargetStatus

type TargetStatus struct {
	// Env is the environment name this status belongs to.
	Env string

	// Server is the server name within that environment.
	Server string

	// Capability is the resolved access level for this target.
	Capability Capability

	// SSHReachable is true when the SSH probe completed without error.
	SSHReachable bool

	// KeyPresent is true when the key file referenced by SSHKeyRef exists on
	// the local filesystem.
	KeyPresent bool

	// DockerOK is true when the docker-info probe over SSH succeeded.
	DockerOK bool

	// LatencyMS is the round-trip time of the SSH probe in milliseconds.
	// Zero if the probe was skipped (local environment, key absent, etc.).
	LatencyMS int

	// Reason provides the human-readable explanation for the assigned
	// Capability. Empty string means capability is CapManage (no caveat).
	Reason string

	// ProbedAt records when these probe results were obtained.
	ProbedAt time.Time
}

TargetStatus is the resolved state of a single server after the capability resolver has run. It is the unit of work for the deployment pipeline.

The Reason field uses the exact strings defined in §5.3 of the architecture spec so that callers can pattern-match without parsing free text.

func Resolve

func Resolve(inv *Inventory, p Prober) []TargetStatus

Resolve evaluates the capability of every server in inv by running probes through p and returns one TargetStatus per server.

The decision algorithm follows §5.1 of the architecture spec:

  1. Local kind env → CapManage, no probe.
  2. Host empty → CapHidden.
  3. SSHKeyRef unset → KeyPresent=false → CapReadOnly + reason "no SSH key for <ref>".
  4. KeyState present=false → CapReadOnly + reason "no SSH key for <ref>".
  5. SSHReachable → if false → CapReadOnly + reason "SSH unreachable (<err class>)".
  6. DockerOK → if false → CapReadOnly + reason "SSH ok, docker not reachable".
  7. Both SSH and Docker ok → CapManage.

Resolve never calls os/exec directly; all I/O is delegated to p.

type Tier added in v1.4.13

type Tier string

Tier states the operational class of an environment.

const (
	TierLocal        Tier = "local"
	TierLocalServers Tier = "local-servers"
	TierProd         Tier = "prod"
)

func DeriveTier added in v1.4.13

func DeriveTier(name, kind string) Tier

DeriveTier supplies the v2 default for an environment without a tier.

Directories

Path Synopsis
Package promote runs an environment promotion as an injected transaction.
Package promote runs an environment promotion as an injected transaction.
Package sim provides a multi-server Docker sshd simulation harness for integration-testing the control-plane pipeline without touching live hosts.
Package sim provides a multi-server Docker sshd simulation harness for integration-testing the control-plane pipeline without touching live hosts.

Jump to

Keyboard shortcuts

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