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

deploy

package
v1.4.15 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package deploy provides deployment management for the nSelf CLI. This file implements the blue-green deploy strategy adapter (T06).

Blue-green deploys:

  • Spin up a "green" stack alongside the live "blue" stack.
  • Run health checks on green.
  • Flip Nginx upstream weights from blue → green (100% flip, no canary phase).
  • Drain blue connections (60s grace via the bluegreen package).
  • Tear down blue.
  • On green failure: abort, keep blue running, log error.

This file is the strategy-path adapter called when the user passes --strategy=blue-green. It delegates all orchestration to the internal bluegreen package and writes outcome to .nself/state/deploy.json.

Package deploy provides deployment management for the nSelf CLI. This file implements the canary deploy strategy adapter (T06).

Canary deploys:

  • Spin up a canary (green) stack alongside the live (blue) stack.
  • Route N% of traffic to green via Nginx weighted upstream.
  • Monitor health during a configurable soak window (default 300s / 5min).
  • On healthy soak: gradually shift traffic 10% → 50% → 100%.
  • On unhealthy soak: revert canary to 0%, tear down green stack, log.

This file is the strategy-path adapter called when the user passes --strategy=canary. It delegates all orchestration to the internal bluegreen package and writes outcome to .nself/state/deploy.json.

Package deploy provides deployment orchestration for nSelf projects. This file implements secrets push over SSH using scp (G-003 / ops-profile).

Purpose: Copy a local .env file to a remote deploy host without echoing values. Inputs: SSHConfig (host + key path), PushSecretsOptions (target, paths, dry-run). Outputs: Remote ~/app/.env written; nothing secret reaches stdout. Constraints: scp must be in PATH; SSH key must be pre-installed on remote. SPORT: deploy subsystem — F08-SERVICE-INVENTORY.md

Package deploy provides deployment management commands for the nSelf CLI. This file implements the SLO-triggered rollback path (S88d.T05).

The SLO watcher (slo-watcher.ts in ping_api) calls:

nself deploy --rollback --service <service> --key <deploy-key>

This package handles that invocation: validates the deploy key, logs to np_auditlog_events, runs the rollback, and returns exit 0 on success or non-zero on failure.

Package deploy provides SSH deployment helpers for nSelf.

Package deploy provides deployment management for the nSelf CLI. This file manages the unified deploy state file at .nself/state/deploy.json. It tracks which strategy was last used, its outcome, and provides the rollback target for `nself deploy rollback`.

Purpose: shared validation for remote-path values that get interpolated into shell command strings executed on remote hosts over SSH (docker compose invocations built with fmt.Sprintf and handed to `ssh` as a single argv element, which the remote shell then parses). Inputs: a candidate remote-path string, from any input source: a CLI flag (`nself deploy --remote-path`, `nself env target add --remote-path`), a parsed .nself/control-plane.yaml inventory file, or an NSELF_REMOTE_PATH_<TARGET> environment variable. Outputs: nil when the path is empty or matches the allowed charset; otherwise a descriptive error naming the offending value. Constraints: this is the single source of truth for the remote-path charset enforced across cmd/commands (deploy, deploy_remote, env target add) and internal/controlplane (inventory Load/synthesize). Centralizing it here (rather than in cmd/commands, which internal/controlplane already imports transitively via internal/deploy) avoids an import cycle while keeping every enforcement point byte-identical. Deliberately an allowlist (reject anything outside a known-safe charset), not a blacklist of known- bad shell metacharacters, per the standing guidance that allowlists are harder to bypass via an overlooked special character.

Index

Constants

View Source
const BackupDir = ".nself/backups"

BackupDir is where pre-deploy tars live, relative to the remote path.

View Source
const BuildNginxMarker = "# Generated by nself build"

BuildNginxMarker is the first-line stamp of every nginx conf the build renders. A host conf.d file counts as marked only when one of its first 5 lines starts with exactly this text (case-sensitive): looser matches take "# hand-written, NOT generated by nself build" or "# Generated by nself ssl add" for ours. A test in cmd/commands keeps it equal to the build's marker.

View Source
const DeployStateFile = ".nself/state/deploy.json"

DeployStateFile is the path relative to project root where deploy state is persisted. All deploy strategies (rolling, blue-green, canary) write to this file.

View Source
const GeneratedMarker = "generated by nself"

GeneratedMarker is the case-insensitive text every generated file carries in its first lines: "GENERATED BY nself ... DO NOT HAND EDIT" and the nginx templates' "Generated by nself build - DO NOT EDIT MANUALLY".

View Source
const LockDir = ".nself/deploy.lock"

LockDir is the lock directory, relative to the remote path.

View Source
const NginxServiceName = "nginx"

NginxServiceName is the compose service the generated stack names nginx.

View Source
const PreDeployTarKeep = 5

PreDeployTarKeep is how many pre-deploy tars a host keeps after a successful deploy (the protected one is extra).

View Source
const ShippedFile = ".nself/deploy-shipped.txt"

ShippedFile lists, one relative path per line, what the last v1.5 deploy shipped to the host. The prune only ever considers paths from this list, so a marker-carrying file nSelf never shipped here (another project's conf) stays.

Variables

View Source
var DataDirs = []string{".volumes"}

DataDirs are the project dirs whose bind mounts hold service data (internal/setup writes .volumes/ to .gitignore for the same reason).

View Source
var RemotePathRe = remote.RemotePathRe

RemotePathRe is the shared remote-path allowlist. It lives in sdk/go/remote (the one implementation); this alias keeps every existing caller and cmd/commands' own alias pointing at the same compiled pattern.

View Source
var SSHKeyRe = regexp.MustCompile(`^[a-zA-Z0-9/_.~-]+$`)

SSHKeyRe is the safe alphabet for an SSH key path. The one definition: the legacy push in cmd/commands aliases it.

Functions

func AcquireLock added in v1.4.14

func AcquireLock(ctx context.Context, r Remote, remotePath, id string) (release func(), err error)

AcquireLock takes the host's deploy lock.

func CheckBuildContexts added in v1.4.14

func CheckBuildContexts(ctx context.Context, r Remote, set ArtifactSet) error

CheckBuildContexts checks every service with a build context in the set.

func CheckNginx added in v1.4.14

func CheckNginx(ctx context.Context, r Remote, remotePath string, model ComposeModel, out io.Writer) error

CheckNginx runs `nginx -t` for the shipped config. out receives the one skip line when the stack has no nginx service.

func CheckNotOverwritingForeign added in v1.4.14

func CheckNotOverwritingForeign(ctx context.Context, r Remote, remotePath string, files, previous []string) error

CheckNotOverwritingForeign refuses (E485 `unmarked file would be overwritten`, nothing sent) when a nginx/ file of the set already exists on the host without the nself generated marker in its first lines and is not in the previous deploy's local record (previous) or in the host's own shipped list (whoever can edit that list can edit the nginx files directly, so it grants nothing; deletes stay driven by the local record): such a file is hand written, or another tool's. The printed paths are always taken from the set, never from the host's output.

func ConfirmStateful added in v1.4.14

func ConfirmStateful(p RecreatePreview, prodClass, yes bool, confirm func(msg string) bool) error

ConfirmStateful applies the D10/D24 rule: on a prod-class target a stateful service in the recreate set needs confirmation (--yes, or a TTY answer from confirm), and a dry-run line that cannot be classified is refused outright (--yes does not cover what the preview could not read). Anything else returns nil. The refusal is E403; nothing has been applied when it is returned.

func DefaultHome added in v1.4.14

func DefaultHome() string

DefaultHome is the operator's home dir for a PathMap.

func DeployKnownHostsPath added in v1.4.13

func DeployKnownHostsPath() string

DeployKnownHostsPath is the dedicated pin file. The operator's SSH file is read only.

func DeployTofuHostsPath added in v1.4.15

func DeployTofuHostsPath() string

DeployTofuHostsPath is nself's own first-seen key file (v1.5 accept-new).

func DeployViaSsh added in v1.1.1

func DeployViaSsh(ctx context.Context, cfg SSHConfig, composePath string) error

DeployViaSsh copies env then compose, promotes env, pulls images and restarts.

func DriftError added in v1.4.14

func DriftError(diffs []string) error

DriftError is E485 with the differences named, or nil when there are none.

func HasuraMetadataApplyCommand added in v1.4.14

func HasuraMetadataApplyCommand(remotePath string) string

HasuraMetadataApplyCommand is the remote metadata apply with a short retry loop, wrapped in sh -c: the for-loop is POSIX syntax that a fish or csh login shell would reject (D-0542). The legacy single-host push uses it too.

func HostKeyOptions added in v1.4.13

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

HostKeyOptions returns accept-new or strict policy after validating the live key.

func HostKeyPolicy added in v1.4.15

func HostKeyPolicy(ctx context.Context, host, env, server string, strict, shipsSecrets bool) ([]string, error)

HostKeyPolicy is the shared host-key policy. shipsSecrets forces strict checking in v1.4 too (EPIC D28, D-0248): secrets never go to an unverified key. Every caller that sends a secret off this machine passes shipsSecrets.

func LayoutDrift added in v1.4.14

func LayoutDrift(want, got TargetSnapshot) []string

LayoutDrift lists the differences between the snapshot the build used (want) and another read of a target (got): plugins, versions and ssl dirs.

func NewBackupID added in v1.4.14

func NewBackupID(now time.Time) string

NewBackupID returns a UTC timestamp plus six random hex characters.

func ParseNginxInclude added in v1.4.14

func ParseNginxInclude(raw string) ([]string, error)

ParseNginxInclude parses the value of NSELF_DEPLOY_NGINX_INCLUDE: comma separated globs relative to nginx/, matched with path.Match per segment (`sites/hand.conf,includes/*.conf`). Blank entries are ignored. An entry that is absolute, not clean, holds a ".." segment, a control character or a bad pattern, or starts with conf.d fails the whole value: nothing is sent.

func PluginDrift added in v1.4.14

func PluginDrift(local []PluginRef, target TargetSnapshot) []string

PluginDrift lists the differences between the local enabled plugin set and the target's: a plugin on one side only, or a version difference.

func PruneMarked added in v1.4.14

func PruneMarked(ctx context.Context, r Remote, remotePath string, candidates []string) ([]string, error)

PruneMarked removes, from the candidates (paths relative to remotePath), the files that carry the generated marker in their first lines. Entries that are not safe project-relative paths (see pruneable) are skipped. A batch in which any path resolves outside the project (or is itself a symlink) is refused whole: nothing in it is deleted and the error names the path. It returns the files it removed.

func PrunePreDeployTars added in v1.4.14

func PrunePreDeployTars(ctx context.Context, r Remote, remotePath string, keep int, protect ...string) ([]string, error)

PrunePreDeployTars removes the pre-deploy tars of remotePath beyond the newest keep, except the absolute paths in protect, and returns the removed names.

func PrunePrevious added in v1.4.14

func PrunePrevious(ctx context.Context, r Remote, remotePath string, keep map[string]bool, recorded []string) ([]string, error)

PrunePrevious removes the files the previous deploy shipped that the new set dropped and that carry the generated marker. What may be removed is decided by recorded, the local record of that deploy (deploy.json); the host's own shipped list (informational) can only narrow it. With no record nothing is removed. It returns the files it removed.

func PushSecrets added in v1.1.11

func PushSecrets(ctx context.Context, cfg SSHConfig, opts PushSecretsOptions) error

PushSecrets copies a local .env file to the remote deploy host over SSH using scp.

Secret values are never printed to stdout; only the target path and host are logged. The operation is idempotent: re-running safely overwrites the remote file.

Resolution order for EnvFile (when not explicitly set):

  1. {ProjectRoot}/.env.{Target}
  2. {ProjectRoot}/.env

The remote host is extracted from SSHConfig.Host which may be:

  • "user@host" or "host" (RemotePath used as-is)
  • "user@host:/remote/path" (path component ignored; RemotePath wins)

func ReapplyRemote added in v1.4.14

func ReapplyRemote(ctx context.Context, o ApplyOptions) error

ReapplyRemote runs the pull and the service-by-service up with the health gate over the manifests already on the host (rollback uses it after the files are back in place), then validates the restored nginx config in a throwaway container before reloading nginx, and applies the hasura metadata.

func RemoteDockerExecCommand added in v1.2.1

func RemoteDockerExecCommand(container string, args ...string) string

RemoteDockerExecCommand builds the shell command string for `docker exec <container> <args...>` run on the remote host. Arguments are shell-quoted individually so container names/args containing spaces are passed through safely.

func RemoteStartMarks added in v1.4.14

func RemoteStartMarks(ctx context.Context, r Remote, remotePath string, files, envFiles []string, service string) (map[string]string, error)

RemoteStartMarks maps the id of each exited container of service to its StartedAt, taken before `up` so a one-shot that runs again can be told from an old exit.

func RemoteStates added in v1.4.14

func RemoteStates(ctx context.Context, r Remote, remotePath string, files, envFiles []string, service string, baseline map[string]string) ([]docker.ContainerState, error)

RemoteStates reads service's containers on the host. baseline (from RemoteStartMarks) turns an exited container that has not started since into docker.StateNotRestarted; nil means no baseline.

func ResolveDirs added in v1.4.14

func ResolveDirs(ctx context.Context, r Remote, remotePath string, dirs []string) (map[string]string, error)

ResolveDirs resolves every dir on the host (cd -P). It returns dir -> real path for the dirs reached through a symlink that stays inside the real remote_path, and fails with E485 (`symlink outside remote_path`) when a dir, or a link inside the tar's sources, resolves elsewhere or cannot be resolved.

func ResolveFrontDirs added in v1.4.15

func ResolveFrontDirs(ctx context.Context, r Remote, front string, files []string) (map[string]string, error)

ResolveFrontDirs resolves frontDirs and the files (relative to front) on the host.

func RestorePreDeployTar added in v1.4.14

func RestorePreDeployTar(ctx context.Context, r Remote, remotePath, tar string, spec RestoreSpec) error

RestorePreDeployTar reverts this deploy's own changes: it rsyncs back the Changed paths the tar holds, then removes the Absent paths. A tampered or inconsistent spec (an unsafe path, an Absent path the tar holds) is refused before anything is touched. It runs on a context that survives the caller's cancellation, bounded by restoreTimeout.

func RollbackRemote added in v1.4.14

func RollbackRemote(ctx context.Context, o RollbackOptions) error

RollbackRemote undoes the last v1.5 remote apply on the host.

func RunRemoteCommand added in v1.2.1

func RunRemoteCommand(ctx context.Context, rt RemoteTarget, command string) (string, error)

RunRemoteCommand runs command on the remote target via SSH and returns combined stdout+stderr. Errors are wrapped with the remote target and command for easy diagnosis; callers that need to detect "command not found on an older CLI" (gap #16, version drift) should inspect the returned error text for the exec/ssh "command not found" markers.

func SaveDeployState added in v1.0.16

func SaveDeployState(projectRoot string, s StrategyState) error

SaveDeployState writes the deploy state to .nself/state/deploy.json. The file is written atomically via a temp file + rename to avoid partial reads. Permissions are 0600 because deploy state contains hostnames and error context.

func ScanDeployKeys added in v1.4.13

func ScanDeployKeys(ctx context.Context, spec remote.HostSpec) ([]remote.HostKey, error)

ScanDeployKeys reads the live host keys. It makes one ssh-keyscan call per algorithm (several algorithms per call trip connection throttling on small sshd hosts), ed25519 first, and tries the next algorithm only when the previous call returned no key. A connect error (no server banner) stops at once: a second attempt cannot succeed. HostKey.Type comes from the scanned line.

func SyncArtifacts added in v1.4.14

func SyncArtifacts(ctx context.Context, r Remote, set ArtifactSet) error

SyncArtifacts copies the set into remotePath in place, then checks the env file modes. It never deletes anything.

func TrustHostKey added in v1.4.13

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

TrustHostKey scans live keys and pins only the key with the offered fingerprint.

func ValidateRemotePath added in v1.3.6

func ValidateRemotePath(path string) error

ValidateRemotePath returns an error when path is non-empty and contains characters outside RemotePathRe's allowed charset. An empty path is treated as valid here — callers that require a non-empty path (e.g. a remote deploy target) must check that separately; ssh.go's DeployViaSsh already falls back to /tmp when the recovered remote path is empty.

One refusal is added to the charset check: a leading "-", which a remote rsync --server or scp would read as an option. ".." segments stay accepted, as they always were. The check itself lives in sdk/go/remote.

func WithInterrupt added in v1.4.14

func WithInterrupt(ctx context.Context) (context.Context, context.CancelFunc)

WithInterrupt returns a context that is cancelled on the first SIGINT, SIGTERM or (unix) SIGHUP. A later signal does not kill the process: it prints one line that it is still finishing the step or restoring. Call stop when the operation ends.

Types

type ApplyOptions added in v1.4.14

type ApplyOptions struct {
	Remote     Remote
	RemotePath string
	// Env names the environment, for the rollback hint in errors.
	Env string
	Set ArtifactSet
	// Expected is the snapshot the build used; the host must still match it.
	// Nil skips that comparison (the local-set comparison still runs).
	Expected     *TargetSnapshot
	LocalPlugins []PluginRef
	// ProdClass: take the verified snapshot and require confirmation for a
	// stateful recreate.
	ProdClass bool
	Yes       bool
	Confirm   func(msg string) bool
	// BackupID is a verified database backup the caller already took for the whole
	// env (one per env, on its primary, before any server is touched). When set,
	// no backup is taken here.
	BackupID string
	// Previous are the paths the env's previous deploy shipped or pruned, from the
	// local record (deploy.json). They alone decide what the prune may remove; the
	// host's own list can only narrow them. Empty: nothing is pruned.
	Previous []string
	// Snapshot takes and verifies a database backup tagged with id and returns
	// its backup id (E206 on failure). Used when ProdClass and BackupID is empty.
	Snapshot func(ctx context.Context, id string) (string, error)
	// StrictHasura makes a failed metadata apply fail the deploy.
	StrictHasura bool
	Clock        docker.Clock
	Out          io.Writer
	Now          func() time.Time
}

ApplyOptions configures ApplyRemote.

type ApplyResult added in v1.4.14

type ApplyResult struct {
	BackupID     string // verified database backup (prod-class only)
	BackupHost   string
	PreDeployTar string
	// Synced is true once the artifact sync started and the host's files were not
	// put back: the deploy record must be written. A deploy that failed before
	// the first `up` and was restored in place reports false (the last good
	// record stays); if that restore itself failed it stays true.
	Synced bool
	// Changed are the paths this deploy shipped or pruned, Absent the subset the
	// host did not have before. Recorded locally; they drive a rollback.
	Changed     []string
	Absent      []string
	Recreated   []string
	FrontRecord // the fronting stack's tar and changed/absent lists (D-0425)
	// Touched is true once the first `up` ran: containers may have changed, so
	// the env's database backup belongs in the record.
	Touched bool
}

ApplyResult records what ApplyRemote did, even when it stopped early.

func ApplyRemote added in v1.4.14

func ApplyRemote(ctx context.Context, o ApplyOptions) (res ApplyResult, err error)

ApplyRemote applies o.Set to the host.

type ArtifactSet added in v1.4.14

type ArtifactSet struct {
	StageDir    string
	RemotePath  string
	Files       []string // relative to StageDir and to RemotePath; env files excluded
	EnvFiles    []string // relative; shipped 0600
	Compose     []string // remote absolute compose files, in -f order
	EnvManifest []string // remote absolute env files, in --env-file order
	// KeepHostEnv: the project .env is in EnvManifest but not shipped. It is the
	// host's own file (the target snapshot reads PLUGIN_DIR from it, the host's
	// nself reads it); the operator's copy would replace it with local values.
	// A sync only creates it, empty and 0600, when the host has none.
	KeepHostEnv bool
	// NotShipped are the nginx/ files of the scratch tree that were left out
	// (relative to RemotePath, sorted): not written by this build and not on the
	// operator's NSELF_DEPLOY_NGINX_INCLUDE allowlist (D-0423).
	NotShipped []string
	// Front is the fronting stack's second artifact root (D-0425); nil for a
	// project that runs its own nginx.
	Front *FrontRoot
}

ArtifactSet is the staged tree and its manifests.

func Stage added in v1.4.14

func Stage(in StageInput) (ArtifactSet, error)

Stage builds the artifact set in in.StageDir.

func (ArtifactSet) KeepSet added in v1.4.14

func (s ArtifactSet) KeepSet() map[string]bool

KeepSet is the set's relative paths as a lookup.

func (ArtifactSet) Without added in v1.4.14

func (s ArtifactSet) Without(paths []string) ArtifactSet

Without returns a copy of the set that leaves out paths (relative to the remote path) from Files and EnvFiles. The receiver is not changed. The staged files stay in the staging dir; they are no longer listed, so nothing syncs them.

type AuditEvent

type AuditEvent struct {
	EventType    string    `json:"event_type"`
	Service      string    `json:"service"`
	Environment  string    `json:"environment"`
	Reason       string    `json:"reason"`
	Outcome      string    `json:"outcome"`
	PriorVersion string    `json:"prior_version,omitempty"`
	Error        string    `json:"error,omitempty"`
	TriggeredBy  string    `json:"triggered_by"`
	Timestamp    time.Time `json:"timestamp"`
}

AuditEvent is written to np_auditlog_events on rollback.

type BlueGreenConfig added in v1.0.16

type BlueGreenConfig struct {
	// ProjectRoot is the nSelf project root (contains docker-compose.yml).
	ProjectRoot string

	// Target is the deploy target: local, staging, prod.
	Target string

	// DryRun prints steps without executing them.
	DryRun bool

	// HealthTimeoutSec overrides the default 30-second health check timeout.
	// Zero means use the default.
	HealthTimeoutSec int

	// ForceMigration disables the migration compatibility check and performs a
	// full blue-green flip even when backward-incompatible migrations are pending.
	ForceMigration bool
}

BlueGreenConfig holds the parameters for a blue-green deploy.

type BlueGreenResult added in v1.0.16

type BlueGreenResult struct {
	// Success is true when the deploy completed without error.
	Success bool

	// Steps is the ordered list of deploy steps with their status.
	Steps []bluegreen.DeployStep

	// Duration is how long the deploy took.
	Duration time.Duration

	// RolledBack is true when an automatic rollback was triggered.
	RolledBack bool

	// Error is set on failure.
	Error string
}

BlueGreenResult is the outcome of a blue-green deploy.

func RunBlueGreen added in v1.0.16

func RunBlueGreen(ctx context.Context, cfg BlueGreenConfig) BlueGreenResult

RunBlueGreen executes a blue-green deploy:

  1. Spins up the green stack alongside blue.
  2. Health-checks green.
  3. Flips Nginx upstream to 100% green (no canary phase).
  4. Health-checks green at 100%.
  5. Stops blue.
  6. Persists state to .nself/state/deploy.json.

On green failure: aborts immediately, keeps blue running, returns error.

type CanaryConfig added in v1.0.16

type CanaryConfig struct {
	// ProjectRoot is the nSelf project root (contains docker-compose.yml).
	ProjectRoot string

	// Target is the deploy target: local, staging, prod.
	Target string

	// InitialPercent is the initial canary traffic percentage (1-99).
	// Defaults to 10 when zero.
	InitialPercent int

	// SoakMinutes is the canary soak period in minutes. Defaults to 5.
	SoakMinutes int

	// ErrorThresholdPct is the error rate percentage that triggers auto-rollback.
	// Defaults to 1.0.
	ErrorThresholdPct float64

	// HealthTimeoutSec overrides the default 30-second health check timeout.
	// Zero means use the default.
	HealthTimeoutSec int

	// ForceMigration disables the migration compatibility check.
	// Required when a migration is not backward-compatible with canary traffic.
	ForceMigration bool

	// DryRun prints steps without executing them.
	DryRun bool
}

CanaryConfig holds the parameters for a canary deploy.

type CanaryResult added in v1.0.16

type CanaryResult struct {
	// Success is true when the deploy completed without error.
	Success bool

	// Steps is the ordered list of deploy steps with their status.
	Steps []bluegreen.DeployStep

	// Duration is how long the deploy took.
	Duration time.Duration

	// FinalPercent is the final canary traffic percentage (100 on success, 0 on rollback).
	FinalPercent int

	// RolledBack is true when an auto-rollback was triggered during the soak.
	RolledBack bool

	// Error is set on failure.
	Error string
}

CanaryResult is the outcome of a canary deploy.

func RunCanary added in v1.0.16

func RunCanary(ctx context.Context, cfg CanaryConfig) CanaryResult

RunCanary executes a canary deploy:

  1. Spins up the canary (green) stack alongside blue.
  2. Health-checks green.
  3. Routes InitialPercent of traffic to green via Nginx weighted upstream.
  4. Monitors green health during the soak window.
  5. On healthy: promotes green to 100% traffic, tears down blue.
  6. On unhealthy: reverts to 0% canary, tears down green, returns error.
  7. Persists state to .nself/state/deploy.json.

type CertDir added in v1.4.14

type CertDir struct {
	Name                      string
	Fullchain, Privkey, Chain bool
}

CertDir is one certificate directory and which PEM files it holds.

func TakeFrontSSL added in v1.4.15

func TakeFrontSSL(ctx context.Context, r Remote, front string) ([]CertDir, error)

TakeFrontSSL lists the certificate dirs of the fronting stack at front.

type ComposeModel added in v1.4.14

type ComposeModel struct {
	Project  string
	Services map[string]ModelService
	Order    []string // `config --services` order
}

ComposeModel is the resolved model of the shipped manifests.

func ReadComposeModel added in v1.4.14

func ReadComposeModel(ctx context.Context, r Remote, remotePath string, files, envFiles []string) (ComposeModel, error)

ReadComposeModel resolves the shipped manifests on the host.

func (ComposeModel) Specs added in v1.4.14

func (m ComposeModel) Specs(name string) (docker.ServiceSpec, bool)

Specs returns the docker.ServiceSpec of a service, ok false if unknown.

func (ComposeModel) Stateful added in v1.4.14

func (m ComposeModel) Stateful(name, remotePath string) bool

Stateful reports whether a service keeps data on the host: a volume mount (named or anonymous), or a bind mount under <remotePath>/<data dir>.

type FrontRecord added in v1.4.15

type FrontRecord struct {
	FrontTar     string
	FrontChanged []string
	FrontAbsent  []string
	// FrontShipped are the front files this deploy shipped (not the pruned ones):
	// the only paths the next deploy may prune.
	FrontShipped []string
	// FrontNames is the server_name set of each shipped file (the file as shipped).
	FrontNames map[string][]string
	// FrontSynced is true once the front sync started (the first front write was
	// allowed). A record carries the front fields only then: a deploy that stopped
	// before it (the re-check refused) changed nothing in the front root, and a
	// rollback must not restore a tar over files the deploy never touched.
	FrontSynced bool
}

FrontRecord is what ApplyResult carries about the front root: its pre-deploy tar and the paths shipped or pruned there (Absent: the shipped ones the host did not have). Empty when the deploy touched no front root.

type FrontRoot added in v1.4.14

type FrontRoot struct {
	StageDir   string   // where the files are staged locally
	RemotePath string   // the fronting stack's directory on the host
	Files      []string // relative to StageDir and RemotePath ("nginx/sites/<x>.conf")
	// Previous are, per host (the Remote label), the front paths this project's
	// previous deploy shipped there (the local deploy.json record); only they may
	// be pruned. Set by the command.
	Previous map[string][]string
	// PreviousNames are, per host and path, the server_name set that deploy shipped
	// there; a path without an entry is unknown (an old record) and is never pruned.
	PreviousNames map[string]map[string][]string
}

FrontRoot is the fronting stack's artifact root.

type KeptConfD added in v1.4.14

type KeptConfD struct {
	Path string
	// DefaultServerIn is set when the file was kept out because the host already
	// declares a default_server in that file (relative to the remote path);
	// empty when the host holds a hand-written file at Path.
	DefaultServerIn string
}

KeptConfD is a conf.d candidate a deploy leaves out of the set.

func KeptHostConfD added in v1.4.14

func KeptHostConfD(ctx context.Context, r Remote, remotePath string, files []string) ([]KeptConfD, error)

KeptHostConfD returns the nginx/conf.d/<name>.conf candidates of files that a deploy must not ship (D-0424): the path exists as a regular file with no line in its first 5 starting with BuildNginxMarker, or is a symlink or anything that is not a regular file. Also, when nginx/conf.d/default.conf is absent on the host and any other regular file under nginx/conf.d*/ or nginx/sites/ has a listen line with default_server, our default.conf is kept out (reason DefaultServerIn), since two default servers fail nginx -t. The previous deploy's record and the host's shipped list are ignored for conf.d: the host file alone decides, so a hand-written default.conf survives even when an earlier deploy shipped one. A missing file or a marked one is otherwise not kept. Printed paths are always taken from files, never from the host's output (the default_server file name is shown with control characters replaced). The caller drops the paths from the set and goes on; this is not an error.

The marker is sent as a pattern with each space written [ ], the same match, so the fake host of the tests (which refuses a script that runs "nself ") does not take the pattern text for a command.

type ModelService added in v1.4.14

type ModelService struct {
	Spec          docker.ServiceSpec
	Image         string
	ContainerName string
	Mounts        []Mount
	Aliases       []string // network aliases, for the nginx -t host entries
}

ModelService is one service of the resolved model.

type Mount added in v1.4.14

type Mount struct {
	Type     string `json:"type"`
	Source   string `json:"source"`
	Target   string `json:"target"`
	ReadOnly bool   `json:"read_only"`
}

Mount is one service mount from the resolved model.

type NginxPolicy added in v1.4.14

type NginxPolicy struct {
	// Generated reports whether a file head carries the generated marker of the
	// build (build.IsGeneratedNginxConf).
	Generated func(head []byte) bool
	// Include are validated allowlist globs relative to nginx/ (ParseNginxInclude).
	Include []string
}

NginxPolicy says which scratch nginx/ files ship.

func (*NginxPolicy) Allows added in v1.4.14

func (p *NginxPolicy) Allows(rel string) bool

Allows reports whether rel (relative to nginx/, slash separated) matches an allowlist glob: same number of segments, each segment path.Match-ed. A conf.d* path never matches.

type PathMap added in v1.4.14

type PathMap struct {
	ScratchRoot  string // the scratch project the build ran in
	OperatorRoot string // the operator's real project root
	PluginDir    string // the local plugin dir the build read
	Home         string // the operator's home dir (reported, never mapped)
	RemotePath   string // the project dir on the target
	// FrontRoot is, for a project fronted by another stack's nginx (D-0425), the
	// local scratch root of that stack: the parent of ScratchRoot. It maps to
	// path.Dir(RemotePath), after ScratchRoot -> RemotePath (longest first).
	FrontRoot string
}

PathMap maps the local roots to the remote layout.

func (PathMap) Leftovers added in v1.4.14

func (m PathMap) Leftovers(s string) []string

Leftovers names the local roots s still contains after a rewrite: scratch, operator, plugin dir and home.

func (PathMap) Remote added in v1.4.14

func (m PathMap) Remote(local string) (string, error)

Remote returns the target path of a local file: under the scratch project it keeps its relative path, under the plugin dir it moves to .nself/plugins.

func (PathMap) RemotePlugins added in v1.4.14

func (m PathMap) RemotePlugins() string

RemotePlugins is where plugin fragments live on the target.

func (PathMap) Rewrite added in v1.4.14

func (m PathMap) Rewrite(s string) string

Rewrite maps every local root in s to the remote layout.

type PluginRef added in v1.4.14

type PluginRef struct {
	Name     string
	Version  string
	Disabled bool
}

PluginRef is one installed plugin.

type PreDeployBackup added in v1.4.14

type PreDeployBackup struct {
	ID     string
	Tar    string   // absolute path on the host
	Absent []string // artifact paths the host did not have at tar time
}

PreDeployBackup is one saved tar.

func CreatePreDeployTar added in v1.4.14

func CreatePreDeployTar(ctx context.Context, r Remote, remotePath, id string, paths []string) (PreDeployBackup, error)

CreatePreDeployTar tars the host's current deployable files, reports which of paths (the artifact set, relative to remotePath) the host does not have yet, and checks the archive reads back.

type PushSecretsOptions added in v1.1.11

type PushSecretsOptions struct {
	// Target is the deploy target name (e.g. "ops", "staging", "prod").
	Target string

	// EnvFile is the local path to the .env file to push.
	// Defaults to {ProjectRoot}/.env.{Target}, falling back to {ProjectRoot}/.env.
	EnvFile string

	// RemotePath is the remote destination path. Defaults to ~/app/.env.
	RemotePath string

	// DryRun logs what would be done without executing scp.
	DryRun bool

	// ProjectRoot is the local project root used to resolve the default EnvFile.
	// Defaults to the current working directory when empty.
	ProjectRoot string
}

PushSecretsOptions configures the secrets push operation.

type RecreatePreview added in v1.4.14

type RecreatePreview struct {
	Recreate []string // services that exist and would be recreated
	Create   []string // services that would be created new
	Stateful []string // the Recreate subset that keeps data
	// Unknown are dry-run lines about a recreate or create that did not parse or
	// did not map to a known service. A production-class deploy refuses on them.
	Unknown []string
}

RecreatePreview is what `up -d` would do.

func PreviewRecreate added in v1.4.14

func PreviewRecreate(ctx context.Context, r Remote, remotePath string, files, envFiles []string, model ComposeModel) (RecreatePreview, error)

PreviewRecreate runs `up -d --dry-run` over the shipped manifests.

type Remote added in v1.4.14

type Remote interface {
	// Run runs one shell command string and returns its stdout. A non-zero
	// exit returns an error that carries the stderr tail.
	Run(ctx context.Context, command string) ([]byte, error)
	// Rsync copies the paths listed (one relative path per line) in filesFrom,
	// taken relative to srcDir, into destDir on the host, in place. flags are
	// extra rsync flags; a delete flag is refused.
	Rsync(ctx context.Context, flags []string, filesFrom, srcDir, destDir string) error
	// Label names the host for state records and messages.
	Label() string
}

Remote runs commands on one remote host.

type RemoteTarget added in v1.2.1

type RemoteTarget struct {
	// SSHTarget is "user@host" (no path component).
	SSHTarget string
	// RemotePath is the absolute path on the remote host where the nSelf
	// project lives (used only for cd-prefixed commands; empty is fine for
	// a plain `docker exec`, which needs no working directory).
	RemotePath string
	// KeyPath is the SSH private key path.
	KeyPath string
}

RemoteTarget describes a resolved remote host for a non-deploy command.

func ResolveRemoteTargetFromEnv added in v1.2.1

func ResolveRemoteTargetFromEnv(target string) (rt RemoteTarget, ok bool)

ResolveRemoteTargetFromEnv builds a RemoteTarget for "staging"/"prod" from the same NSELF_DEPLOY_HOST_<TARGET> / <TARGET>_DEPLOY_HOST env-var convention used by `nself deploy`. Returns ok=false when no host is configured for target (e.g. "local", or a target with no env var set) — callers should fall back to local execution in that case.

type RestoreSpec added in v1.4.14

type RestoreSpec struct {
	Changed []string
	Absent  []string
}

RestoreSpec says what a restore may touch: Changed are the paths this deploy shipped or pruned, Absent the subset that did not exist before it. Both come from this process (or the local deploy.json), not from the host.

type RollbackConfig

type RollbackConfig struct {
	// Service is the nSelf service to roll back (e.g., "ping_api").
	Service string

	// DeployKey is the deploy authorization key (NSELF_DEPLOY_KEY).
	// Required — no anonymous rollbacks.
	DeployKey string

	// Reason is a human-readable reason for the rollback.
	// Set by the SLO watcher: "slo-watcher: <alert description>"
	Reason string

	// Environment is "prod", "staging", etc. Defaults to "prod".
	Environment string

	// MaxRetries is the maximum number of rollback retry attempts. Default: 1.
	MaxRetries int

	// ProjectRoot is the nSelf project root directory. Used to locate
	// docker-compose.yml and state files.
	ProjectRoot string
}

RollbackConfig holds the parameters for a deploy rollback.

type RollbackOptions added in v1.4.14

type RollbackOptions struct {
	Remote     Remote
	RemotePath string
	Tar        string // PreDeployTar of the record
	BackupID   string // BackupID of the record; empty off prod-class
	// Changed and Absent come from the local deploy.json record of this host.
	Changed []string
	Absent  []string
	// DBRestored: the record says the database backup was already restored; it is
	// not restored again. OnDBRestored runs right after a successful restore,
	// before the re-apply, so the caller can persist it.
	DBRestored   bool
	OnDBRestored func() error
	// FrontTar, FrontChanged and FrontAbsent are the record of the fronting
	// stack's root (D-0425); all empty for a deploy that did not touch one.
	FrontTar     string
	FrontChanged []string
	FrontAbsent  []string
	StrictHasura bool
	Clock        docker.Clock
	Out          io.Writer
}

RollbackOptions configures RollbackRemote.

type RollbackResult

type RollbackResult struct {
	// Success is true if the rollback completed without error.
	Success bool

	// Service is the service that was rolled back.
	Service string

	// PriorVersion is the version that was restored.
	PriorVersion string

	// Error is set if rollback failed.
	Error string

	// AuditEventID is the ID written to np_auditlog_events.
	AuditEventID string

	// Duration is how long the rollback took.
	Duration time.Duration

	// UIState is one of the 7 UI states for the rollback.
	UIState RollbackUIState
}

RollbackResult is the outcome of a deploy rollback.

func ExecuteRollback

func ExecuteRollback(ctx context.Context, cfg RollbackConfig) RollbackResult

ExecuteRollback performs a service rollback. It: 1. Validates the deploy key 2. Discovers the prior container image tag 3. Re-deploys the prior image 4. Logs an audit event 5. Returns a RollbackResult with the UI state

type RollbackUIState

type RollbackUIState string

RollbackUIState represents the 7 possible states for rollback UI.

const (
	UIStateLoading     RollbackUIState = "loading"           // rollback in progress
	UIStateEmpty       RollbackUIState = "empty"             // no prior version to roll back to
	UIStateError       RollbackUIState = "error"             // rollback command failed
	UIStatePopulated   RollbackUIState = "populated"         // rollback complete + verified
	UIStateOffline     RollbackUIState = "offline"           // no staging connection
	UIStatePermDenied  RollbackUIState = "permission-denied" // no deploy key
	UIStateRateLimited RollbackUIState = "rate-limited"      // circuit breaker engaged
)

type SSHConfig added in v1.1.1

type SSHConfig struct {
	// Host is the remote target in "user@host:/remote/path" format.
	// NSELF_DEPLOY_HOST is the conventional env var name.
	Host string
	// RemotePath is the separately configured install directory for canonical hosts.
	RemotePath string

	// KeyPath is the path to the SSH private key.
	// Falls back to NSELF_DEPLOY_KEY_PATH, then ~/.ssh/id_ed25519.
	KeyPath string

	// Follow streams container logs after a successful deploy until the
	// context is cancelled (e.g. Ctrl-C from the user).
	Follow bool

	// EnvFile, when set, is a local env file rsynced to <remote path>/.env.<EnvName>
	// right after the compose file, so the host gets the env the compose was
	// built from. EnvName must be a plain name (a-z, 0-9, _ and -).
	EnvFile string
	EnvName string
	// InventoryEnv is the inventory key used by the host-key enrolment command.
	InventoryEnv string
	// ServerName is the inventory key used by the host-key enrolment command.
	ServerName string
}

SSHConfig holds parameters for an SSH-based remote deploy.

func SSHConfigFromEnv added in v1.1.1

func SSHConfigFromEnv(target string) SSHConfig

SSHConfigFromEnv builds an SSHConfig from environment variables.

type SSHRemote added in v1.4.14

type SSHRemote struct {
	Spec remote.HostSpec
	// Opts are the ssh options placed before the HostSpec argv (-i, host key
	// policy, ForwardAgent=no).
	Opts []string
}

SSHRemote is the real Remote.

func NewSSHRemote added in v1.4.14

func NewSSHRemote(ctx context.Context, host, keyPath, env, server string) (*SSHRemote, error)

NewSSHRemote parses host (E484 on a bad string) and builds the strict host key policy for it (E487 for an unknown key).

func (*SSHRemote) Label added in v1.4.14

func (r *SSHRemote) Label() string

Label returns the canonical host string.

func (*SSHRemote) Rsync added in v1.4.14

func (r *SSHRemote) Rsync(ctx context.Context, flags []string, filesFrom, srcDir, destDir string) error

Rsync implements Remote.

func (*SSHRemote) Run added in v1.4.14

func (r *SSHRemote) Run(ctx context.Context, command string) ([]byte, error)

Run implements Remote.

type StageInput added in v1.4.14

type StageInput struct {
	Map         PathMap
	StageDir    string   // a fresh empty dir
	Compose     []string // local compose files in order
	EnvFiles    []string // local env files in order (the compose env manifest)
	EnvSnapshot string   // local resolved env snapshot, shipped as .env.<EnvName> (optional)
	EnvName     string
	// Nginx decides which scratch nginx/ files ship; nil ships every non-conf.d
	// file (and no conf.d file). See NginxPolicy.
	Nginx *NginxPolicy
	// FrontScratch is the local scratch root of the fronting stack (the parent
	// of Map.ScratchRoot) and FrontedBy its NGINX_FRONTED_BY name; both empty
	// for a project that runs its own nginx. See artifacts_front.go.
	FrontScratch, FrontedBy string
}

StageInput is what Stage reads.

type Strategy added in v1.0.16

type Strategy string

Strategy is the deploy strategy used.

const (
	StrategyRolling   Strategy = "rolling"
	StrategyBlueGreen Strategy = "blue-green"
	StrategyCanary    Strategy = "canary"
)

type StrategyState added in v1.0.16

type StrategyState struct {
	// SchemaVersion is the version of the StrategyState schema.
	// Set to 1 on all writes. Readers treat 0 (missing field) as the legacy default.
	SchemaVersion int `json:"schema_version"`

	// Strategy is the deploy strategy used.
	Strategy Strategy `json:"strategy"`

	// Target is the deploy target: local, staging, prod.
	Target string `json:"target"`

	// StartedAt is the timestamp when the deploy started.
	StartedAt time.Time `json:"started_at"`

	// CompletedAt is the timestamp when the deploy completed (success or failure).
	CompletedAt time.Time `json:"completed_at"`

	// Success reports whether the deploy succeeded.
	Success bool `json:"success"`

	// CanaryPercent is the final canary traffic split at completion (0 or 100 for non-canary).
	CanaryPercent int `json:"canary_percent"`

	// RolledBack is true when the deploy triggered an automatic rollback.
	RolledBack bool `json:"rolled_back"`

	// Error is the failure reason when Success is false.
	Error string `json:"error,omitempty"`

	// BlueGreenStateFile is the path to the underlying blue/green state file,
	// populated when strategy is blue-green or canary.
	BlueGreenStateFile string `json:"blue_green_state_file,omitempty"`

	// BackupID is the verified database backup taken before a v1.5 remote apply
	// on a production-class target; BackupHost is the host it lives on, and
	// PreDeployTar the in-place file backup (every tier). `deploy rollback`
	// restores from them. Empty on every other deploy.
	BackupID     string `json:"backup_id,omitempty"`
	BackupHost   string `json:"backup_host,omitempty"`
	PreDeployTar string `json:"pre_deploy_tar,omitempty"`
	// PreDeployTars maps every host the deploy synced to its pre-deploy tar, so
	// `deploy rollback` restores each server of the env, not just the primary.
	PreDeployTars map[string]string `json:"pre_deploy_tars,omitempty"`
	// PreDeployChanged and PreDeployAbsent are, per host, the relative paths the
	// deploy shipped or pruned and the subset the host did not have before. A
	// rollback restores and removes exactly these, never anything read from the host.
	PreDeployChanged map[string][]string `json:"pre_deploy_changed,omitempty"`
	PreDeployAbsent  map[string][]string `json:"pre_deploy_absent,omitempty"`
	// FrontTars, FrontChanged and FrontAbsent are the same per host for the
	// fronting stack's root of a fronted project (D-0425): the front tar and the
	// nginx/sites paths shipped or pruned there. Absent on every other deploy, so
	// a record without them rolls back as before.
	FrontTars    map[string]string   `json:"front_tars,omitempty"`
	FrontChanged map[string][]string `json:"front_changed,omitempty"`
	FrontAbsent  map[string][]string `json:"front_absent,omitempty"`
	// FrontShipped is, per host, the front files that deploy shipped (not the pruned
	// ones): the only paths the next deploy may prune there. Without it nothing is.
	FrontShipped map[string][]string `json:"front_shipped,omitempty"`
	// FrontNames is, per host, the server_name set of each shipped front file: the
	// next deploy replaces or prunes a recorded path only while the host file still has it.
	FrontNames map[string]map[string][]string `json:"front_names,omitempty"`
	// DBRestoredAt is set (RFC 3339) right after `nself backup restore` succeeded,
	// before the re-apply, so a retried rollback never restores the database twice.
	DBRestoredAt string `json:"db_restored_at,omitempty"`
	// HostOutcomes is, per server name, what the deploy did there: "ok", "failed:
	// <err>" (changes kept) or "not changed: <err>" (refused or restored). Success
	// is true only when every server of the env is "ok".
	HostOutcomes map[string]string `json:"host_outcomes,omitempty"`
}

StrategyState records the outcome of the last deploy for a given strategy.

func LoadDeployState added in v1.0.16

func LoadDeployState(projectRoot string) (StrategyState, error)

LoadDeployState reads the deploy state from .nself/state/deploy.json. Returns a zero-value StrategyState and nil when no state file exists (fresh install). Any other read failure and a corrupt file are returned as errors, never as an empty state (D-0144).

type TargetSnapshot added in v1.4.14

type TargetSnapshot struct {
	PluginDir string
	Plugins   []PluginRef
	SSL       []CertDir
	// FrontSSL is the fronting stack's <front>/ssl/certificates layout for a
	// fronted project (TakeFrontSSL); nil for a project that fronts itself.
	FrontSSL []CertDir
}

TargetSnapshot is what a target looks like to the build.

func TakeTargetSnapshot added in v1.4.14

func TakeTargetSnapshot(ctx context.Context, r Remote, remotePath string) (TargetSnapshot, error)

TakeTargetSnapshot reads the snapshot of the host behind r.

type Transport added in v1.4.15

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

Transport builds ssh and rsync argv for one host.

func NewTransport added in v1.4.15

func NewTransport(spec remote.HostSpec, keyPath string, policy []string) (*Transport, error)

NewTransport validates keyPath and every option token and returns the builder.

func (*Transport) Operand added in v1.4.15

func (x *Transport) Operand(path string) string

Operand is the "[user@]host:path" rsync and scp operand (no port).

func (*Transport) RsyncArgv added in v1.4.15

func (x *Transport) RsyncArgv(flags []string, dest string, srcs ...string) ([]string, error)

RsyncArgv is the rsync argv that copies srcs (absolute local paths) to dest on the host. flags are long options such as "--delete". dest must pass remote.ValidateCopyPath.

func (*Transport) SSHArgv added in v1.4.15

func (x *Transport) SSHArgv(command string) []string

SSHArgv is the ssh argv for command: options, port, "--", destination, command.

Directories

Path Synopsis
Package archguard checks image platforms before a remote deploy changes a host.
Package archguard checks image platforms before a remote deploy changes a host.
Package bluegreen implements zero-downtime blue/green and canary deploys for the nSelf CLI (B47 + B48).
Package bluegreen implements zero-downtime blue/green and canary deploys for the nSelf CLI (B47 + B48).
Package pintest gives tests a HOME whose directory chain passes the pinned-host-keys ancestor rule (sdk/go/remote, sshd secure_path style): no directory above the pin store may be group or world writable unless it is a sticky directory such as /tmp.
Package pintest gives tests a HOME whose directory chain passes the pinned-host-keys ancestor rule (sdk/go/remote, sshd secure_path style): no directory above the pin store may be group or world writable unless it is a sticky directory such as /tmp.
Package progress writes contract:cli.deploy-progress v1: the NDJSON stream that `nself deploy --json --stream` and `nself deploy promote <src> <tgt> --json --stream` print.
Package progress writes contract:cli.deploy-progress v1: the NDJSON stream that `nself deploy --json --stream` and `nself deploy promote <src> <tgt> --json --stream` print.

Jump to

Keyboard shortcuts

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