Categorygithub.com/develatio/scp
modulepackage
0.0.2
Repository: https://github.com/develatio/scp.git
Documentation: pkg.go.dev

# README

Secure Copy Protocol implemented in Go

GoReport Widget GoPkg Widget

Overview

Production-ready Secure Copy Protocol (aka: SCP) implemented in Go with well documentation and neat dependency.

Introduction

Secure Copy Protocol uses Secure Shell (SSH) to transfer files between host on a network.

There is no RFC that defines the specifics of the protocol. This package simply implements SCP against the OpenSSH's scp tool, thus you can directly transfer files to/from *uinx system within your Go code, as long as the remote host has OpenSSH installed.

Features

  • Copy file from local to remote.
  • Copy file from remote to local.
  • Copy from buffer to remote file. (e.g: copy from bytes.Reader)
  • Copy from remote file to buffer. (e.g: copy to os.Stdout)
  • Recursively copy directory from local to remote.
  • Recursively copy directory from remote to local.
  • Set permission bits for transferred files.
  • Set timeout/context for transfer.
  • Preserve the permission bits and modification time at transfer.
  • No resources leak. (e.g: goroutine, file descriptor)
  • Low memory consuming for transferring huge files.
  • TODO:
    • Transfer speed limit.
    • Performance benchmark/optimization for lots of small files.
  • Won't support:
    • Copy file from remote to remote.

Install

go get github.com/povsister/scp

Example usage

This package leverages golang.org/x/crypto/ssh to establish a SSH connection to remote host.

Error handling are omitted in examples!

Copy a file to remote

// Build a SSH config from username/password
sshConf := scp.NewSSHConfigFromPassword("username", "password")

// Build a SSH config from private key
privPEM, err := ioutil.ReadFile("/path/to/privateKey")
// without passphrase
sshConf, err := scp.NewSSHConfigFromPrivateKey("username", privPEM)
// with passphrase
sshConf, err := scp.NewSSHConfigFromPrivateKey("username", privPEM, passphrase)

// Dial SSH to "my.server.com:22".
// If your SSH server does not listen on 22, simply suffix the address with port.
// e.g: "my.server.com:1234"
scpClient, err := scp.NewClient("my.server.com", sshConf, &scp.ClientOption{})

// Build a SCP client based on existing "golang.org/x/crypto/ssh.Client"
scpClient, err := scp.NewClientFromExistingSSH(existingSSHClient, &scp.ClientOption{})

defer scpClient.Close()


// Do the file transfer without timeout/context
err = scpClient.CopyFileToRemote("/path/to/local/file", "/path/at/remote", &scp.FileTransferOption{})

// Do the file copy with timeout, context and file properties preserved.
// Note that the context and timeout will both take effect.
fo := &scp.FileTransferOption{
    Context: yourCotext,
    Timeout: 30 * time.Second, 
    PreserveProp: true,
}
err = scpClient.CopyFileToRemote("/path/to/local/file", "/path/at/remote", fo)

Copy a file from remote

// Copy the file from remote and save it as "/path/to/local/file".
err = scpClient.CopyFileFromRemote("/path/to/remote/file", "/path/to/local/file", &scp.FileTransferOption{})

// Copy the remote file and print it in Stdout.
err = scpClient.CopyFromRemote("/path/to/remote/file", os.Stdout, &scp.FileTransferOption{})

Copy from buffer to remote as a file

// From buffer
buffer := []byte("something excited")
reader := bytes.NewReader(buffer)

// From fd
// Note that its YOUR responsibility to CLOSE the fd after transfer.
reader, err := os.Open("/path/to/local/file")
defer reader.Close()


// Note that the reader must implement "KnownSize" interface except os.File
// For the content length must be provided before transfer.
// The last part of remote location will be used as file name at remote.
err := scpClient.CopyToRemote(reader, "/path/to/remote/file", &scp.FileTransferOption{})

Recursively copy a directory to remote

// recursively copy to remote
err := scpClient.CopyDirToRemote("/path/to/local/dir", "/path/to/remote/dir", &scp.DirTransferOption{})

// recursively copy to remote with timeout, context and file properties.
// Note that the context and timeout will both take effect.
do := &scp.DirTransferOption{
    Context: yourContext,
    Timeout: 10 * time.Minute,
    PreserveProp: true,
}
err:= scpClient.CopyDirToRemote("/path/to/local/dir", "/path/to/remote/dir", do)

// recursively copy directory contents to remote. Does not create the
// source directory on the target side, like `scp -r /path/to/dir/* ...`
do := &scp.DirTransferOption{
    ContentOnly: true,
}
err := scpClient.CopyDirToRemote("/path/to/local/dir", "/path/to/remote/dir", do)

Recursively copy a directory from remote

// recursively copy from remote.
// The content of remote dir will be save under "/path/to/local".
err := scpClient.CopyDirFromRemote("/path/to/remote/dir", "/path/to/local", &scp.DirTransferOption{})

Progress

// Defines an anonymous function for ReceivePassthrough that conf the
// Writer attribute of PassthroughCopy to track and display the upload 
// progress. The new Writer writes data to the original writer, calculates
// the upload progress, and prints it as a percentage.

type Progress struct {
    TotalSize int64
    Writed int
}

func (p *Progress) Draw(n) () {
    p.Writed = n
    progress := float64(p.writed) / float64(p.TotalSize) * 100
    fmt.Printf("progress: %.2f%%\n", progress)
}

func (p *Progress) Close() error {
    return nil
}

var bar *Progress
do := &scp.DirTransferOption{
    ObserverCallback: func(oet scp.ObserveEventType, ti scp.TransferInfo) {
      if oet == scp.ObserveEventStart {
        bar = &Progress{TotalSize: ti.TotalSize()}
        return
      }
      bar.Draw(ti.TransferredSize())
    },
}
err = scpClient.CopyDirToRemote("/path/to/local/dir", "/path/to/remote/dir", do)

Something you need to know

SCP is a light-weighted protocol which implements file transfer only. It does not support advanced features like: directory listing, resume from break-point.

So, it's commonly used for transferring some small-size, temporary files. If you heavily depend on the file transfer, you may consider using SFTP instead.

Another thing you may notice is that I didn't put context.Context as the first argument in function signature. Instead, it's located in TransferOption. This is intentional because it makes the API also light-weighted.

License

MIT License

# Functions

NewClient returns a SSH client with SCP capability.
NewClientFromExistingSSH returns a SSH client with SCP capability.
NewSSHConfigFromPassword returns a *ssh.ClientConfig with ssh.Password AuthMethod and 3 seconds timeout for connecting the server.
NewSSHConfigFromPrivateKey returns a *ssh.ClientConfig with ssh.PublicKey AuthMethod and 3 seconds timeout for connecting the server.

# Constants

No description provided by the author
No description provided by the author
No description provided by the author

# Variables

DebugMode controls the debug output.
DefaultDirPerm holds default permission bits for transferred directories.
DefaultFilePerm holds default permission bits for transferred files.
DirectoryPreReads sets the num of pre-read files/directories for recursively transferring a directory.
No description provided by the author
ErrNoClientOption indicates a non-nil ClientOption should be provided.
ErrNoTransferOption indicate a non-nil TransferOption should be provided.
1MB for small size transfer improvements.
32kB, the common buffsize.

# Structs

Client has the "golang.org/x/crypto/ssh/Client" embedded, so it can be used as normal SSH client with additional SCP features.
ClientOption contains several configurable options for SCP client.
DirTransferOption holds the transfer options for directory.
FileTransferOption holds the transfer options for file.

# Interfaces

KnownSize is intended for reader whose size is already known before reading.
TransferInfo provides detailed information about a file transfer operation, allowing access to key attributes such as the file name, path, total size, amount of data transferred, last update time, and any errors that may have occurred during the transfer.

# Type aliases

No description provided by the author