filetransfer

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package filetransfer implements file filtering and SFTP upload for docker-deploy.

Index

Constants

This section is empty.

Variables

View Source
var ErrDeployCancelled = errors.New("deploy cancelled by user")

ErrDeployCancelled is returned by Upload() when the user declines the repeat-deploy confirmation prompt. Callers should treat this as a clean exit (print "Deploy cancelled." and return nil), not as an error.

Functions

func ShellQuote

func ShellQuote(s string) string

ShellQuote wraps s in single quotes for safe use in shell commands, escaping any embedded single quotes using the '\" technique. This handles paths derived from validated config values (remoteBase is from Resolve() which validates via ParseHost; staging dir name uses only alphanumerics + timestamp integer — T-03-05).

func ShouldExclude

func ShouldExclude(relPath string, excludes []string) bool

ShouldExclude reports whether relPath (relative to the walk root, using forward slashes) matches any pattern in excludes.

Pattern matching rules:

  • Directory patterns ending in "/" match: the exact directory name, any path that starts with the pattern, or any path component matching the directory name (to handle deep paths like "a/node_modules/b").
  • Other patterns use filepath.Match semantics against both the basename and the full path.

func SudoExec added in v0.9.0

func SudoExec(client *gossh.Client, cmd string, creds *SudoCreds, warnedOnce *bool, verbose bool) error

SudoExec runs cmd on the remote with automatic privilege escalation fallback.

Step order (D-11):

  1. Direct: sshRun(client, cmd, nil) — no sudo
  2. Cached: if creds.pw != nil, sshRun with creds.pw
  3. Passwordless: sudo -n sh -c <cmd> (NOPASSWD sudoers) 3b. Keychain: if creds.Host/User are set, check macOS Keychain for stored password
  4. Interactive: prompt up to 3 times; on success cache password in creds.pw; offer to save to macOS Keychain if creds.Host/User are set

creds and warnedOnce are in/out params shared across all SudoExec calls within a single Upload() — ensures the interactive prompt fires at most once per deploy (SC-6, D-12).

func Upload

func Upload(ctx context.Context, client *gossh.Client, localDir, remoteBase string, excludes []string, creds *SudoCreds, force bool, warnedOnce *bool, verbose bool) (int, error)

Upload copies all non-excluded files from localDir to a staging directory on the remote, then atomically renames the staging dir to the final target.

remoteBase is the target directory path on the remote (e.g. "/opt/myapp"). excludes is the list of exclude patterns (from Config.Excludes). force skips the replace-confirmation prompt on repeat deploys. verbose controls per-file output and SSH command logging:

  • When verbose=true: per-file lines " -> relative/path" are written to os.Stderr; each SSH command (mkdir, mv, rm) and its exit code are logged to os.Stderr before/after execution; warnedOnce is never set to true so every sudo warning prints. When !force and existsBefore, the pre-confirm diff block prints local and remote file lists (truncated at 20) before the "Replace all contents?" prompt.
  • When verbose=false: per-file lines are suppressed; SSH command lines are suppressed; warnedOnce behavior is unchanged (first warning prints, rest are suppressed).

Atomic swap strategy:

  1. Create staging dir: /tmp/docker-deploy-<unixNanoTimestamp> (/tmp is always writable by the SSH user via SFTP, even when target is root-owned)
  2. Upload all files into staging dir maintaining relative path structure
  3. Ensure remoteBase exists (mkdir -p, falling back to interactive sudo with up to 3 password attempts). If target cannot be created: warn, leave staged files, return error.
  4. Via SSH session exec: if remoteBase exists, mv to .old-<timestamp>, mv staging to remoteBase, rm -rf the .old dir; if absent, just mv staging to remoteBase

If WalkFiles returns 0 files, Upload returns an error.

Progress: prints "Uploading N files..." before starting to os.Stdout. Per-file lines go to os.Stderr only when verbose=true.

Per CLAUDE.md: sessions are NOT reusable — each SSH exec uses a fresh NewSession(). SFTP wraps the existing *gossh.Client — no second TCP connection.

Returns the number of files actually transferred on success.

func WalkFiles

func WalkFiles(localDir string, excludes []string) ([]string, error)

WalkFiles walks localDir and returns all relative file paths that are NOT excluded by ShouldExclude(path, excludes). Directories are never returned. The returned slice is sorted in lexicographic order.

Types

type SudoCreds added in v0.9.0

type SudoCreds struct {
	Host string // remote hostname (e.g. "myserver.example.com")
	User string // SSH/sudo username (e.g. "deploy")
	// contains filtered or unexported fields
}

SudoCreds holds a sudo password as bytes so it can be zeroed after use. The zero value (nil pw) means no password has been captured yet.

Host and User identify the remote target for macOS Keychain operations. When both are non-empty, SudoExec will check the Keychain before prompting interactively and offer to save a newly entered password after first use. Leaving them empty disables Keychain integration and preserves the existing behaviour (direct → cached → passwordless sudo → interactive prompt).

func (*SudoCreds) Zero added in v0.9.0

func (c *SudoCreds) Zero()

Zero zeroes the password bytes and nils the slice to prevent it lingering in process memory after Upload() returns (golang.org/x/crypto convention).

Jump to

Keyboard shortcuts

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