// Package sftp contains the implementation for an SFTP uploader package sftp import ( "context" "fmt" "net" "os" "path" "git.luzifer.io/luzifer/publish-vod/pkg/uploader" "github.com/Luzifer/go_helpers/v2/fieldcollection" "github.com/sirupsen/logrus" pkgSFTP "github.com/pkg/sftp" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) type ( impl struct{} ) // New returns a SFTP Uploader func New() uploader.Uploader { return impl{} } func (impl) Prepare(context.Context, uploader.Opts) error { return nil } func (impl) UploadFile(_ context.Context, opts uploader.Opts) error { socket := os.Getenv("SSH_AUTH_SOCK") conn, err := net.Dial("unix", socket) if err != nil { return fmt.Errorf("opening SSH_AUTH_SOCK: %w", err) } agentClient := agent.NewClient(conn) config := &ssh.ClientConfig{ User: opts.Config.MustString("user", nil), Auth: []ssh.AuthMethod{ // Use a callback rather than PublicKeys so we only consult the // agent once the remote server wants it. ssh.PublicKeysCallback(agentClient.Signers), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), //#nosec G106 } client, err := ssh.Dial("tcp", opts.Config.MustString("host", nil), config) if err != nil { return fmt.Errorf("dialing SSH server: %w", err) } defer func() { if err := client.Close(); err != nil { logrus.WithError(err).Error("closing SSH client (leaked fd)") } }() sftpClient, err := pkgSFTP.NewClient(client) if err != nil { return fmt.Errorf("creating sftp client: %w", err) } defer func() { if err := sftpClient.Close(); err != nil { logrus.WithError(err).Error("closing SFTP client (leaked fd)") } }() remoteFile := path.Join(opts.Config.MustString("path", nil), path.Base(opts.Filename)) f, err := sftpClient.OpenFile(remoteFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC) if err != nil { return fmt.Errorf("opening remote file: %w", err) } defer func() { if err := f.Close(); err != nil { logrus.WithError(err).Error("closing SFTP file (leaked fd)") } }() progressReader := opts.ProgressBar.NewProxyReader(opts.Content) if _, err = f.ReadFrom(progressReader); err != nil { return fmt.Errorf("uploading video: %w", err) } opts.FinalMessage("Video uploaded to %s", remoteFile) return nil } func (impl) ValidateConfig(config *fieldcollection.FieldCollection) error { for _, key := range []string{"host", "path", "user"} { if v, err := config.String(key); err != nil || v == "" { return fmt.Errorf("key %q must be non-empty string", key) } } return nil }