Documentation
¶
Overview ¶
Package deploy runs the production deploy pipeline over SSH: preflight, render, sync, build, before_deploy hooks, up, after_deploy hooks, health probe, and commit (the .pier/state.json write that records the active image tag for `pier rollback`). The package owns the typed error contract (ExitError, Kind) and the SSH client used for every remote command.
Index ¶
- Constants
- Variables
- func AbortedError() error
- func BootstrapEnv(ctx context.Context, cfg SSHConfig, password string, opts BootstrapOpts) error
- func Build(ctx context.Context, r runner, dir, project, sha string, onLine func(string)) error
- func BuildError(err error) error
- func BuildLocalImage(ctx context.Context, dir, php, project, sha string, onLine func(string)) error
- func ConfigError(err error) error
- func DockerError(err error) error
- func EnsureClockSynced(ctx context.Context, r stdinRunner, password string, ...) error
- func EnsureDeployPath(ctx context.Context, r cmdRunner, path string) error
- func ExecDownError() error
- func HealthURL(cfg config.Config, env string) string
- func NetworkError(err error) error
- func NotBootstrappedError(env string) error
- func PortInUseError(ports []int) error
- func PreflightError(err error) error
- func Probe(ctx context.Context, cfg HealthConfig) error
- func ProbeBootstrap(ctx context.Context, r stdinRunner) (bool, error)
- func ProbeEnv(ctx context.Context, cfg SSHConfig) (bool, error)
- func Provision(ctx context.Context, r stdinRunner, password, user string, ...) error
- func ProvisionDeployPath(ctx context.Context, r stdinRunner, password, user, path string, ...) error
- func RemoteBuildError(host string, err error) error
- func RemoteBuildImage(ctx context.Context, r runner, dir, php, project, sha string, ...) error
- func RemoteCommandError(host string, err error) error
- func RemoteDockerError(host string, err error) error
- func RemoteExec(ctx context.Context, cfg SSHConfig, dir string, args []string) error
- func RemoteHookError(host string, err error) error
- func RemoteShell(ctx context.Context, cfg SSHConfig, dir string) error
- func RemoteUpError(host string, err error) error
- func ResolvedURL(cfg config.Config, env string) string
- func Rollback(ctx context.Context, st stateStore, r runner, dir, project, target string) error
- func SSHError(err error) error
- func SaveState(dir string, s *State) error
- func SessionError(host string, err error) error
- func Tag(ctx context.Context, r runner, project, sha string) error
- func TransferImage(ctx context.Context, saver imageSaver, dst *Client, image string, ...) (int64, error)
- func Up(ctx context.Context, r runner, dir string) error
- func UpError(err error) error
- func UserError(err error) error
- func ValidateSudo(ctx context.Context, r stdinRunner, password string, ...) error
- func VerifyBootstrap(ctx context.Context, r stdinRunner, password, user string, ...) error
- type BootstrapOpts
- type Client
- func (c *Client) Close() error
- func (c *Client) InteractiveShell(ctx context.Context, dir string) error
- func (c *Client) ReadFile(ctx context.Context, remotePath string) ([]byte, error)
- func (c *Client) Run(ctx context.Context, cmd string) ([]byte, []byte, error)
- func (c *Client) RunStdin(ctx context.Context, cmd string, stdin string) ([]byte, []byte, error)
- func (c *Client) RunStream(ctx context.Context, cmd string, onLine func(string)) error
- func (c *Client) RunStreamStdin(ctx context.Context, cmd, stdin string, onStdout, onStderr func(string)) ([]byte, error)
- func (c *Client) StreamIn(ctx context.Context, cmd string, in io.Reader, onLine func(string)) error
- func (c *Client) StreamOut(ctx context.Context, cmd string, out io.Writer, onLine func(string)) error
- func (c *Client) SyncDir(ctx context.Context, local, remote string, excludes []string) error
- func (c *Client) WriteFile(ctx context.Context, remotePath string, b []byte) error
- type Event
- type ExitError
- type HealthConfig
- type Kind
- type Logger
- type Pipeline
- type SFTPStateStore
- type SSHConfig
- type State
- type StatusReport
- type StatusRunner
Constants ¶
const ( ExitOK = 0 // ExitGeneral is the fallback for errors that don't match a more // specific category below. ExitGeneral = 1 // ExitPreflight is returned when SSH, port-probe, or // pier.toml validation fails before any image is built. ExitPreflight = 2 // ExitBuild is returned when `docker compose build` fails on // the remote host. ExitBuild = 3 // ExitUp is returned when `docker compose up -d` fails (or the // post-up health check fails) on the remote host. ExitUp = 4 // ExitExecDown is returned by `pier shell` / `pier exec` when // the laravel.test container isn't running. ExitExecDown = 5 // ExitPortInUse is returned by `pier dev` when the pre-flight // host-port probe finds one or more pier-owned host ports already // in use on 127.0.0.1. The user must edit [dev.ports] in pier.toml // to remap before retrying. ExitPortInUse = 6 // ExitHooks is returned when a before_deploy or after_deploy // command fails on the remote host (a failed hook aborts the // deploy). ExitHooks = 7 // ExitAborted is returned when the user aborts an interactive TUI // (q / Ctrl+C). 130 = 128 + SIGINT, the POSIX shell convention. ExitAborted = 130 )
Process exit codes used by every pier command. The CLI maps these to os.Exit in cmd/pier/main.go.
const ClockSyncThreshold = 60
ClockSyncThreshold is the max |remote - local| offset in seconds tolerated before pier force-sets the remote clock. Freshly-reset VMs boot with a stale RTC; apt rejects signed Release files whose dates fall outside the (wrong) guest clock, so even minutes of skew break provisioning.
Variables ¶
var ( // ErrNotBootstrapped wraps the deploy-side fail-fast: the server // is not provisioned and `pier bootstrap` must run first. ErrNotBootstrapped = errors.New("server not bootstrapped") // ErrAlreadyBootstrapped is returned by BootstrapEnv when the // probe passes; the CLI prints "already bootstrapped — skipping". ErrAlreadyBootstrapped = errors.New("already bootstrapped") // ErrSudoWrongPassword is returned by ValidateSudo when the sudo // password is rejected. The CLI re-prompts once. ErrSudoWrongPassword = errors.New("wrong sudo password") // ErrSudoNotSudoers is returned when the user has no sudo rights // at all; the CLI aborts with instructions. ErrSudoNotSudoers = errors.New("deploy user has no sudo rights") )
Sentinel errors for the bootstrap flow. The CLI inspects these with errors.Is to decide whether to skip, re-prompt, or abort.
var ( ErrBuild = errors.New("build") ErrUp = errors.New("up") ErrExecDown = errors.New("container not running") ErrPortInUse = errors.New("port in use") ErrHooks = errors.New("deploy hook") ErrAborted = errors.New("aborted") )
Sentinel errors. The CLI's PrintError inspects these to pick a category and a hint; tests use errors.Is to assert failure modes.
var ErrPreflight = errors.New("deploy: preflight failed")
ErrPreflight is the sentinel for every error returned by Dial (empty host, missing key, bad permissions, handshake failure). Callers use errors.Is to surface a KindConfig error with a clear message.
var ErrShellNoTTY = errors.New("remote shell requires a terminal")
not a terminal; an interactive remote shell would be unusable.
Functions ¶
func AbortedError ¶
func AbortedError() error
func BootstrapEnv ¶
BootstrapEnv runs the full one-time provisioning flow for one server: probe (unless Force), sudo validation, clock sync, provision, deploy path creation (unless Path is empty), verify. Returns ErrAlreadyBootstrapped when the probe passes and Force is false.
func Build ¶
Build runs `docker compose --env-file .env.production -f docker-compose.prod.yml build --pull` on the remote host, streaming each output line to onLine. Used as stage 4 of the deploy pipeline. The --env-file is passed so ${...} interpolation in the compose file resolves instead of warning; --pull keeps the image fresh.
func BuildError ¶
func BuildLocalImage ¶
BuildLocalImage runs plain `docker build` on the local machine in dir (the project root), streaming stdout/stderr lines to onLine. Used as the build stage of the local_machine builder mode.
func ConfigError ¶
func DockerError ¶
func EnsureClockSynced ¶
func EnsureClockSynced(ctx context.Context, r stdinRunner, password string, onStdout, onStderr func(string)) error
EnsureClockSynced compares the remote clock to the local clock and, when they differ by more than ClockSyncThreshold seconds, force-sets the remote clock from the local one under sudo (`date -s @<epoch>`). Needs sudo only when a correction is required. On correction it re-reads the remote epoch, verifies the skew is now within ClockSyncThreshold (erroring out if the set did not apply), and emits one line via onStdout: `remote clock was Ns off; corrected to <RFC3339>`.
func EnsureDeployPath ¶
EnsureDeployPath creates the deploy path as the deploy user without sudo, so the deploy path is writable for the file sync. Fails when the parent directory is not writable; the deploy preflight turns that into an actionable error.
func ExecDownError ¶
func ExecDownError() error
func HealthURL ¶
HealthURL returns the URL the health probe GETs for env. With a domain the probe targets https://<domain>:<port>/up (port resolved via laravelpkg.WebPort, default 443) with normal TLS verification — an end-to-end check that exercises the real Let's Encrypt certificate (the DNS preflight already verified the domain points at the deploy host). Without a domain it probes the deploy host IP with the resolved "laravel" port, so health checks pass before DNS or /etc/hosts entries exist.
func NetworkError ¶
func NotBootstrappedError ¶
NotBootstrappedError builds the deploy-side fail-fast error: the server was never provisioned, so `pier bootstrap <env>` must run first. Wrapped as a preflight error so the CLI exits with code 2.
func PortInUseError ¶
PortInUseError builds an error for a pre-flight port-probe collision. ports is the list of host ports that were already listening on 127.0.0.1 when `pier dev` started. The user edits [dev.ports] in pier.toml to remap and retries.
func PreflightError ¶
PreflightError wraps err as a config-category ExitError with code ExitPreflight. Used for SSH handshake failures, missing pier.toml fields, and other pre-build problems.
func Probe ¶
func Probe(ctx context.Context, cfg HealthConfig) error
Probe repeatedly GETs cfg.URL until it returns a 2xx, the Timeout elapses, or MaxAttempts is reached. The interval doubles each attempt, capped at 10 s. Used as stage 6 of the deploy pipeline; a failed probe triggers Rollback.
func ProbeBootstrap ¶
ProbeBootstrap reports whether the deploy user can run docker without sudo: `command -v docker` and `docker info` must both succeed. A non-zero exit counts as "not bootstrapped" (false, nil); only session-level failures (connection resets, etc.) return a non-nil error.
func ProbeEnv ¶
ProbeEnv dials cfg and runs the bootstrap probe. Convenience for the CLI's skip check before prompting for the password.
func Provision ¶
func Provision(ctx context.Context, r stdinRunner, password, user string, onStdout, onStderr func(string)) error
Provision installs Docker Engine + the compose plugin with the official get.docker.com script and adds user to the docker group, both under sudo. Idempotent — safe to re-run with --force.
func ProvisionDeployPath ¶
func ProvisionDeployPath(ctx context.Context, r stdinRunner, password, user, path string, onStdout, onStderr func(string)) error
ProvisionDeployPath creates the env's deploy path on the remote host under sudo and hands it to the deploy user: `mkdir -p <path> && chown <user>:<user> <path>`. Idempotent, so `--force` re-runs are safe. The user is quoted the same way Provision quotes it.
func RemoteBuildError ¶
RemoteBuildError is BuildError stamped with the SSH host the build ran on, so the CLI can render a remote-aware hint.
func RemoteBuildImage ¶
func RemoteBuildImage(ctx context.Context, r runner, dir, php, project, sha string, onLine func(string)) error
RemoteBuildImage runs plain `docker build` on a remote build server inside dir (the synced project root), streaming output lines to onLine. Used as the build stage of the build_server builder mode. dir, php, project, and sha are shell-quoted so a hostile value in pier.toml cannot inject commands into the remote shell (F2).
func RemoteCommandError ¶
RemoteCommandError wraps a failed remote command. When err carries an *ssh.ExitError, pier's exit code mirrors the remote exit status; otherwise the failure is a session-level error and gets ExitGeneral. RemoteHost is stamped on both so the CLI renders the remote-aware hint.
func RemoteDockerError ¶
RemoteDockerError is a general docker failure stamped with the SSH host, used by `pier status <env>` when a remote probe command fails.
func RemoteExec ¶
RemoteExec runs a one-off command in the app service of the prod compose file on the remote host, streaming stdout/stderr to pier's own streams as they arrive. The remote exit status becomes pier's exit code via RemoteCommandError; a status-0 exit returns nil. Cancelling ctx aborts the in-flight session.
func RemoteHookError ¶
RemoteHookError is a failed before_deploy / after_deploy command stamped with the SSH host it ran on.
func RemoteShell ¶
RemoteShell opens an interactive bash in the app service of the prod compose file on the remote host and returns when the remote shell exits. The remote exit status becomes pier's exit code. Cancelling ctx aborts the in-flight session.
func RemoteUpError ¶
RemoteUpError is UpError stamped with the SSH host the up ran on.
func ResolvedURL ¶
ResolvedURL returns the public URL for the deployed env: scheme and port resolved from the env's domain and the "laravel" port (default 443 with a domain, 80 for the plain-HTTP default, or the per-env override from [deploy.<env>.ports.laravel]). The host is the env's domain when it resolves (DNS or /etc/hosts); otherwise it falls back to the deploy host IP, so the printed URL is usable before DNS entries point the domain at the server.
func Rollback ¶
Rollback retags the previous deploy's image to the tag the prod compose file actually references and re-runs Up. target is that tag without the project prefix: "latest" in host_server mode (the compose keeps the build context and references <project>:latest) and "current" in the image modes (<project>:current). The "previous" image comes from .pier/state.json on the deploy host (written by Pipeline.commit at the end of every successful deploy). Returns an error if state.json is missing or if the previous image tag is empty (i.e. there is no prior deploy to roll back to). st may be nil, in which case local disk is used (unit tests); the pipeline always passes sftpStateStore.
func SaveState ¶
SaveState atomically writes s to .pier/state.json in dir. The .pier/ directory is created with 0755 if missing; the file is written to a sibling .tmp first and renamed into place so a crash mid-write can't corrupt the deploy record.
func SessionError ¶
SessionError wraps an SSH session-level failure (session creation, pty or command start, stream setup) on host.
func Tag ¶
Tag retags the just-built <project>:latest image to <project>:<sha> (the immutable deploy record) and to <project>:current (the live alias that Rollback overwrites).
func TransferImage ¶
func TransferImage(ctx context.Context, saver imageSaver, dst *Client, image string, onLine func(string)) (int64, error)
TransferImage streams image from saver into dst's docker daemon via `docker load` and returns the number of bytes streamed. Both sides run concurrently: the saver's output feeds an io.Pipe whose read side is the docker load session's stdin. A failure on either side aborts the transfer; the saver's error takes precedence so the reported cause matches where the image came from.
func Up ¶
Up runs `docker compose --env-file .env.production -f docker-compose.prod.yml up -d --wait --wait-timeout 120 --remove-orphans` on the remote host and then reloads the webserver's caddy. Used as stage 5 of the deploy pipeline and by Rollback. The --env-file is required: the compose file interpolates ${DB_PASSWORD}/${APP_KEY} from it (without it compose warns and the app container gets blank secrets and 500s). The --wait makes compose return only once every service with a healthcheck (postgres, redis, the sidecars) is healthy — `up -d` alone returns as soon as containers start, so a fresh postgres volume still initializing would refuse connections from the very first after_deploy hook (`SQLSTATE[08006] Connection refused`), while the same hook succeeds minutes later in an interactive shell. --wait-timeout bounds that wait so a never- healthy service fails the deploy instead of hanging it. The reload is needed because the sync writes bind-mounted files in place (inode preserved), so a changed Caddyfile is visible to the webserver container, but caddy only reads config at start/reload and compose does not recreate a service whose spec is unchanged. Reloading unconditionally is harmless when nothing changed; the error is ignored because a freshly created container already loaded the current config. --remove-orphans stops and removes containers whose service is no longer in the compose file — exactly the sidecars the per-env render dropped — while named volumes (mysql_data, s3_data, ...) are preserved.
func ValidateSudo ¶
func ValidateSudo(ctx context.Context, r stdinRunner, password string, onStdout, onStderr func(string)) error
ValidateSudo proves the password works by running `sudo -S -v`. Returns ErrSudoWrongPassword or ErrSudoNotSudoers on failure.
func VerifyBootstrap ¶
func VerifyBootstrap(ctx context.Context, r stdinRunner, password, user string, onStdout, onStderr func(string)) error
VerifyBootstrap confirms the daemon runs, the compose plugin is present, and the user is a member of the docker group. Group membership only applies to new SSH connections, so the group file is checked directly (getent) instead of re-running docker.
Types ¶
type BootstrapOpts ¶
type BootstrapOpts struct {
// Force skips the probe and re-provisions even when the server
// is already bootstrapped.
Force bool
// User is the deploy user that gets docker group membership.
User string
// Path is the env's deploy directory ([deploy.<env>].path),
// created with sudo and chowned to User. Empty means no path to
// create.
Path string
// OnStdout/OnStderr stream each remote output line as it
// arrives; may be nil.
OnStdout func(string)
OnStderr func(string)
}
BootstrapOpts is the parameter set for BootstrapEnv.
type Client ¶
type Client struct {
Config SSHConfig
// contains filtered or unexported fields
}
Client is a thin wrapper around an *ssh.Client. It implements the internal runner interface (Run / RunStream) used by every other stage of the deploy pipeline.
func Dial ¶
Dial opens an SSH connection to cfg and returns a ready Client. When cfg.KnownHostsPath is set the remote host key is verified trust-on-first-use (F3); the CLI always sets it to ~/.ssh/known_hosts. Key auth is tried first; if the key file is missing or the server rejects every offered key (an auth-class failure), Dial falls back to the password from cfg.Password or cfg.PasswordPrompt and retries once. A password fallback is attempted only when a password source exists; otherwise the original handshake error is returned.
func (*Client) Close ¶
Close shuts down the underlying SSH connection. Safe to call when the connection was never established.
func (*Client) InteractiveShell ¶
InteractiveShell runs the remote interactive bash session: local terminal size → pty request, raw-mode stdin with restore (also on error paths), SIGWINCH → WindowChange forwarding, and byte-copying of stdin/stdout/stderr through the session. Requires a TTY on local stdin. Cancelling ctx aborts the in-flight session.
func (*Client) ReadFile ¶
ReadFile reads a remote file over SFTP on the already-open SSH connection. Returns (nil, nil) when the path does not exist.
func (*Client) Run ¶
Run executes cmd on the remote host and returns the captured stdout, stderr, and the session error. Used by Tag, Up, and Rollback where the caller needs to inspect the output (or where streaming is unnecessary).
func (*Client) RunStdin ¶
RunStdin executes cmd on the remote host with stdin piped from the given string. Used by bootstrap to feed the sudo password to `sudo -S` without it ever appearing in the command string (and thus in remote process listings or logs).
func (*Client) RunStream ¶
RunStream executes cmd on the remote host and invokes onLine for each line of stdout or stderr as it arrives. onLine may be invoked concurrently from two goroutines (the stdout and stderr readers), so callbacks must be safe for concurrent use. Used by Build, which needs to surface `docker compose build` progress — and, on failure, the compose validation errors that docker writes to stderr — in the deploy TUI in real time. When the remote command exits non-zero, the returned error carries the last runStreamTailSize streamed lines so the failure is diagnosable without re-running.
func (*Client) RunStreamStdin ¶
func (c *Client) RunStreamStdin(ctx context.Context, cmd, stdin string, onStdout, onStderr func(string)) ([]byte, error)
RunStreamStdin executes cmd on the remote host with stdin piped from the given string, invoking onStdout/onStderr for each line as it arrives and returning the captured stderr. Used by bootstrap to stream provisioning output while feeding the sudo password to `sudo -S` without it ever appearing in the command string.
func (*Client) StreamIn ¶
StreamIn executes cmd on the remote host with stdin fed from in and invokes onLine for each stderr line as it arrives. The stdin path is binary-safe (no line scanning on the input side): it pipes `docker save` output into a remote `docker load`. stderr is line-streamed because docker load writes its progress and errors as lines. On a non-zero exit the returned error carries the last runStreamTailSize stderr lines.
func (*Client) StreamOut ¶
func (c *Client) StreamOut(ctx context.Context, cmd string, out io.Writer, onLine func(string)) error
StreamOut executes cmd on the remote host piping its stdout into out and invoking onLine for each stderr line as it arrives. The stdout path is binary-safe (io.Copy, no line scanning): it streams `docker save` output from a build server into a sink. On a non-zero exit the returned error carries the last runStreamTailSize stderr lines.
func (*Client) SyncDir ¶
SyncDir copies the local directory tree to remote over SFTP, reusing the already-open SSH connection. Paths matched by excludes (see pathExcluded) are skipped; excluded directories are pruned. Remote parent directories are created on demand and file modes and modification times are preserved. ctx is honored between files; a single large file copy is not interruptible mid-stream.
type Event ¶
type Event struct {
Time time.Time `json:"time"`
Phase string `json:"phase,omitempty"`
Level string `json:"level,omitempty"`
Message string `json:"message"`
Data map[string]any `json:"data,omitempty"`
}
Event is a single structured log record. The CLI's stdLogger marshals this to JSON when --json is set; the TUI logger ignores it. Time is filled in by the receiver if zero.
type ExitError ¶
type ExitError struct {
Code int
Kind Kind
// RemoteHost is the SSH host a failed remote command ran on.
// Empty means the failure was local.
RemoteHost string
Err error
}
ExitError wraps a sentinel with a process exit code and a Kind (config / docker / ssh / network / user / unknown). The CLI's PrintError reads Kind to color the output and to look up a category-specific hint. RemoteHost, when non-empty, marks the error as a remote (SSH) command failure so PrintError renders a remote-aware hint instead of the local pier hint.
type HealthConfig ¶
type HealthConfig struct {
URL string
Timeout time.Duration
Interval time.Duration
MaxAttempts int
}
HealthConfig drives Probe: the URL to GET, the overall Timeout, the Interval between attempts, and the MaxAttempts safety cap. The defaults come from DefaultHealthConfig.
func DefaultHealthConfig ¶
func DefaultHealthConfig(cfg config.Config, env string) HealthConfig
DefaultHealthConfig returns a sensible default HealthConfig for a deploy env: GET to the deploy host's web endpoint (scheme and port resolved from the env's domain (see HealthURL)), 60 s total timeout, 2 s base interval, 30 attempts (interval doubles each attempt up to a 10 s cap).
type Kind ¶
type Kind int
Kind categorizes an error so the CLI can pick a color and a hint when rendering. The values are stable; new categories append.
const ( // KindUnknown is the zero value; the CLI renders these errors // without a [kind] label and with no hint. KindUnknown Kind = iota // KindConfig is for pier.toml parse / validation failures and // for pre-deploy checks that fail before any Docker work. KindConfig // KindDocker is for `docker compose` failures on the local or // remote host. KindDocker // KindSSH is for SSH handshake, auth, and session failures. KindSSH // KindNetwork is for DNS, registry, and pull-time connectivity // problems. KindNetwork // KindUser is for user-actionable problems (port collisions, // interactive aborts). KindUser )
type Logger ¶
type Logger interface {
Emit(Event)
PhaseStart(name string)
PhaseEnd(name string, err error)
Log(level, format string, args ...any)
JSON() bool
Writer() io.Writer
}
Logger is the contract every deploy event sink implements. The stdLogger (CLI) emits plain lines or JSON; the TUI logger pushes events into the Bubble Tea channel. The Pipeline writes to whichever Logger the caller hands it.
type Pipeline ¶
type Pipeline struct {
Config *config.Config
Env string
DeployEnv config.DeployConfig
Logger Logger
SSH SSHConfig
// BuildSSH is the SSH config of the dedicated build server, used
// only when [deploy.<env>].builder is "build_server". Dialed in
// preflight like the host connection.
BuildSSH SSHConfig
Health HealthConfig
Now func() time.Time
// contains filtered or unexported fields
}
Pipeline is the top-level deploy driver. One Pipeline is constructed per `pier deploy <env>` invocation and Run is called exactly once.
func (*Pipeline) Run ¶
Run executes the full deploy pipeline: preflight, render, sync, build, before_deploy hooks, up, after_deploy hooks, health probe, commit. A failing before_deploy hook aborts the deploy (before the new release starts); a failing after_deploy hook, up, or health probe retags the previous image and re-deploys it (Rollback) before the error is returned. Now is set to time.Now if nil so tests can pin the timestamp written to state.json.
type SFTPStateStore ¶
type SFTPStateStore struct {
Client *Client
}
SFTPStateStore reads and writes .pier/state.json over SFTP on the deploy SSH connection, so the deploy record lives on the remote host where Rollback and `pier status <env>` can find it. Client is the already-dialed deploy connection.
func (SFTPStateStore) WriteState ¶
type SSHConfig ¶
type SSHConfig struct {
Host string
User string
KeyPath string
Port int
Password string
PasswordPrompt func() (string, error)
KnownHostsPath string
}
SSHConfig is everything Dial needs to open a connection: target host, login user, path to the private key on the local filesystem, and the TCP port (0 means 22). When the server rejects the key (or the key file does not exist), Dial falls back to a password: Password wins, then PasswordPrompt (which may be nil to forbid prompting). KnownHostsPath names the OpenSSH known_hosts file to verify the remote host against; when it is set, the host key is verified trust-on-first-use (TOFU) — an unknown host is accepted and recorded, a known host whose key changed is rejected (MITM defense, F3). When empty, the host key is not verified.
type State ¶
type State struct {
Current string `json:"current"`
Previous string `json:"previous"`
DeployedAt string `json:"deployed_at"`
DeployedBy string `json:"deployed_by"`
}
State is the parsed .pier/state.json from a remote project. The JSON tags are the wire format; do not rename without a migration.
func LoadState ¶
LoadState reads .pier/state.json from dir and decodes it. Returns (nil, nil) when the file does not exist (a fresh project, no deploys yet) so the caller can treat "no state" as a normal condition.
func (*State) HasPrevious ¶
HasPrevious reports whether State carries a non-empty Previous image tag. Rollback short-circuits to "no previous deploy" when this is false.
type StatusReport ¶
type StatusReport struct {
Containers string // `docker compose -f docker-compose.prod.yml ps` output
Disk string // `df -h <path>` output
DockerDisk string // `docker system df` output
State *State // parsed .pier/state.json; nil when the file is absent
Healthy bool // single HTTP GET of health.URL returned 2xx
}
StatusReport is the result of a `pier status <env>` probe of a remote host: raw command output for containers and disk, the last deploy record, and a single HTTP health verdict.
func RemoteStatus ¶
func RemoteStatus(ctx context.Context, de config.DeployConfig, health HealthConfig, r StatusRunner) (*StatusReport, error)
RemoteStatus probes the remote host behind r: container state, deploy-path and docker disk usage, the last deploy record, and one HTTP health check against health.URL. A missing state file is normal (a project with no deploys yet) and yields a nil State. A failed health probe sets Healthy=false instead of failing the probe; a failed probe command (compose, df, or docker system df) returns an error. For the state file, a failed `cat` (e.g. no deploys yet) yields a nil State; output that cannot be parsed returns an error.
type StatusRunner ¶
type StatusRunner interface {
Run(ctx context.Context, cmd string) ([]byte, []byte, error)
Close() error
}
StatusRunner is the subset of *Client that RemoteStatus needs: Run to capture command output and Close to release the connection. The CLI dials a real client and passes it; tests inject a fake.