-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 9914b3e
Showing
40 changed files
with
2,575 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
test-results/ | ||
tmp/ | ||
routes/ | ||
vendor/*/ | ||
.idea | ||
.DS_Store | ||
node_modules | ||
log/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# Mini sFTP client | ||
|
||
This is a mini web based sFTP client written on Go using Revel Framework | ||
|
||
## Quick Start | ||
|
||
### Download and run | ||
|
||
* Access [releases page](https://github.com/anikitenko/mini-sftp-client/releases) | ||
and pickup the latest version for your OS | ||
* Download and unzip archive locally | ||
* Run run.bat for Windows OR run.sh for Linux/OS X | ||
* When prompted enter port to listen on | ||
* Access http://127.0.0.1:[port you choose] | ||
|
||
### Run from sources | ||
|
||
Prerequisite: | ||
|
||
* Go 1.6+ | ||
|
||
Install Revel: | ||
|
||
go get -u github.com/revel/cmd/revel | ||
|
||
Get mini sftp client: | ||
|
||
go get -u github.com/anikitenko/mini-sftp-client | ||
|
||
Run app: | ||
|
||
revel run github.com/anikitenko/mini-sftp-client | ||
|
||
### Benefits and Key features | ||
- [x] Nothing to install: unzip and run. Use different tabs for different connections | ||
- [x] Runs on Linux, OS X, Windows | ||
- [x] Run client for all interfaces and access client from mobile device and manage files | ||
- [x] Run client on your file server (possible Linux based, Windows, OS X) and access from your desktop or mobile | ||
|
||
## Usage | ||
|
||
Once you navigate to http://127.0.0.1:[port you choose] you should see the following screen: | ||
|
||
[[https://github.com/anikitenko/mini-sftp-client/blob/master/doc-images/first-screen.png|alt=first screen]] | ||
|
||
##### Notes: | ||
* If you are able to authenticate without password on your server, you may ignore that field | ||
* During establishing SSH connection client will try to use .ssh/id_rsa and .ssh/id_dsa if client finds them | ||
* Unsure about connection? Use Test button | ||
* Changing connection name also changes title of the page. | ||
Open client in a couple of tabs, change connection name and | ||
you will be able to distinguish different connections | ||
|
||
##### Establishing connection: | ||
|
||
[[https://github.com/anikitenko/mini-sftp-client/blob/master/doc-images/connecting.png|alt=connecting]] | ||
|
||
##### Notes: | ||
* After you successfully established connection, client will try to detect home directories no matter what is your local OS | ||
* Button to Test connection is disabled after successful connection | ||
* This is because you can enter credentials to 1 server and if you test | ||
for another, input data remains and button to ReConnect also remains, | ||
so silly click on it will cause all data to load from your another server | ||
|
||
[[https://github.com/anikitenko/mini-sftp-client/blob/master/doc-images/like-double-tab.gif|alt=like double tab]] | ||
[[https://github.com/anikitenko/mini-sftp-client/blob/master/doc-images/like-double-tab-local.gif|alt=like double tab local]] | ||
|
||
##### Notes: | ||
* "Like double tab" works on Windows, OS X and Linux OS | ||
|
||
##### Downloading files and using search: | ||
|
||
[[https://github.com/anikitenko/mini-sftp-client/blob/master/doc-images/download-search.gif|alt=download and search]] | ||
|
||
##### Notes: | ||
* Search works the same for remote files as for local | ||
* Search will not search for files globally, it's only sorting files which are exist | ||
|
||
##### Quick buttons | ||
* Quick buttons for remote files: | ||
* Go Back: every time you navigate, client will save paths and on click button will return you to previous path | ||
* Go Home: button will navigate you to initial directory which was opened during connection | ||
* Go UP: navigates you to parent directory | ||
* Refresh: refresh current directory | ||
* Quick buttons for local files: | ||
* Go Back: every time you navigate, client will save paths and on click button will return you to previous path | ||
* Go Home: button will navigate you to initial directory which was opened during connection | ||
* Go UP: navigates you to parent directory | ||
* Create New Directory: create new empty directory and navigate to it | ||
* Refresh: refresh current directory |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package controllers | ||
|
||
import ( | ||
"github.com/revel/revel" | ||
) | ||
|
||
type App struct { | ||
*revel.Controller | ||
} | ||
|
||
func (c App) Index() revel.Result { | ||
return c.Render() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package controllers | ||
|
||
import ( | ||
"os/user" | ||
"path/filepath" | ||
|
||
"github.com/revel/revel" | ||
logger "github.com/sirupsen/logrus" | ||
) | ||
|
||
func (c App) ConnectViaSSH() revel.Result { | ||
var resultMessage []string | ||
data := make(map[string]interface{}) | ||
|
||
if connection := c.EstablishSSHConnection(); connection != nil { | ||
return connection | ||
} | ||
|
||
defer SSHsession.Close() | ||
defer SSHclient.Close() | ||
|
||
if currentUserPathBytes, err := SSHsession.Output("echo -n $PWD"); err == nil { | ||
data["remote_path"] = string(currentUserPathBytes) | ||
} else { | ||
data["remote_path"] = "" | ||
logger.Warnf("Unable to determine remote path: %v", err) | ||
resultMessage = append(resultMessage, "Unable to determine remote path") | ||
} | ||
|
||
var homeDirectory string | ||
if username, err := user.Current(); err != nil { | ||
if currentAbsPath, err := filepath.Abs("./"); err == nil { | ||
homeDirectory = currentAbsPath | ||
} else { | ||
homeDirectory = "" | ||
} | ||
} else { | ||
homeDirectory = username.HomeDir | ||
} | ||
|
||
data["local_path"] = homeDirectory | ||
|
||
data["errors"] = resultMessage | ||
|
||
data["local_path_separator"] = string(filepath.Separator) | ||
|
||
response := CompileJSONResult(true, "", data) | ||
return c.RenderJSON(response) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package controllers | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
"os/user" | ||
"path/filepath" | ||
|
||
logger "github.com/sirupsen/logrus" | ||
"golang.org/x/crypto/ssh" | ||
) | ||
|
||
func createSSHSession(ip, userName, password, port string) (*ssh.Client, *ssh.Session, error) { | ||
username, _ := user.Current() | ||
userIdRSAFile := username.HomeDir + string(filepath.Separator) + ".ssh" + string(filepath.Separator) + "id_rsa" | ||
userIdDSAFile := username.HomeDir + string(filepath.Separator) + ".ssh" + string(filepath.Separator) + "id_dsa" | ||
|
||
sshConfig := &ssh.ClientConfig{} | ||
sshConfig.User = userName | ||
sshConfig.Auth = []ssh.AuthMethod{} | ||
if password != "" { | ||
sshConfig.Auth = append(sshConfig.Auth, ssh.Password(password)) | ||
} | ||
if _, err := os.Stat(userIdRSAFile); err == nil { | ||
sshConfig.Auth = append(sshConfig.Auth, PublicKeyFile(userIdRSAFile)) | ||
} | ||
if _, err := os.Stat(userIdDSAFile); err == nil { | ||
sshConfig.Auth = append(sshConfig.Auth, PublicKeyFile(userIdDSAFile)) | ||
} | ||
|
||
sshConfig.HostKeyCallback = ssh.InsecureIgnoreHostKey() | ||
|
||
client, err := ssh.Dial("tcp", ip+":"+port, sshConfig) | ||
if err != nil { | ||
logger.Warn(err.Error()) | ||
return nil, nil, errors.New("cannot dial") | ||
} | ||
|
||
session, err := client.NewSession() | ||
if err != nil { | ||
logger.Warn(err.Error()) | ||
return nil, nil, errors.New("unable to create session") | ||
} | ||
|
||
return client, session, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package controllers | ||
|
||
import ( | ||
"archive/tar" | ||
"compress/gzip" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/revel/revel" | ||
logger "github.com/sirupsen/logrus" | ||
) | ||
|
||
func (c App) Download() revel.Result { | ||
sourcePath := c.Params.Get("source_path") | ||
localPath := c.Params.Get("local_path") | ||
isDir := c.Params.Get("is_dir") | ||
fileNamePost := c.Params.Get("file_name") | ||
fileName := filepath.Base(fileNamePost) | ||
|
||
if connection := c.EstablishSSHConnection(); connection != nil { | ||
return connection | ||
} | ||
|
||
defer SSHclient.Close() | ||
|
||
if dirOrNot, err := strconv.ParseBool(isDir); err != nil { | ||
logger.Warnf("We don't understand if it's a file or directory: %v", err) | ||
response := CompileJSONResult(false, "We don't understand if it's a file or directory") | ||
return c.RenderJSON(response) | ||
} else { | ||
if dirOrNot { | ||
stdoutNewPipe, err := SSHsession.StdoutPipe() | ||
if err != nil { | ||
logger.Warnf("Cannot create a pipe to download folder: %v", err) | ||
response := CompileJSONResult(false, "Cannot create a pipe to download folder") | ||
return c.RenderJSON(response) | ||
} | ||
tempArchiveName := fmt.Sprintf("%sdownload_%v.tar.gz", localPath+string(filepath.Separator), time.Now().UnixNano()) | ||
tempArchiveFile, err := os.OpenFile(tempArchiveName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) | ||
if err != nil { | ||
logger.Warnf("Cannot open temp archive to write to: %v", err) | ||
response := CompileJSONResult(false, "Cannot open temp archive to write to") | ||
return c.RenderJSON(response) | ||
} | ||
if err := SSHsession.Start("tar cz --directory='" + sourcePath + "' '" + fileName + "'"); err != nil { | ||
logger.Warnf("Problem with running command via SSH: %v", err) | ||
response := CompileJSONResult(false, "Problem with running command via SSH") | ||
return c.RenderJSON(response) | ||
} | ||
_, err = io.Copy(tempArchiveFile, stdoutNewPipe) | ||
if err != nil { | ||
logger.Warnf("Problem with copying archive via SSH: %v", err) | ||
response := CompileJSONResult(false, "Problem with copying archive via SSH") | ||
return c.RenderJSON(response) | ||
} | ||
|
||
//if err := SSHsession.Wait(); err != nil { | ||
// logger.Warnf("Something is wrong while waiting for command to complete: %v", err) | ||
// response := CompileJSONResult(false, "Something is wrong while waiting for command to complete") | ||
// return c.RenderJSON(response) | ||
//} | ||
|
||
SSHsession.Close() | ||
tempArchiveFile.Close() | ||
|
||
archiveOpen, err := os.Open(tempArchiveName) | ||
if err != nil { | ||
if err := os.Remove(tempArchiveName); err != nil { | ||
logger.Warnf("Unable to remove temp archive: %v", err) | ||
} | ||
logger.Warnf("Problem with opening temp archive: %v", err) | ||
response := CompileJSONResult(false, "Problem with opening temp archive") | ||
return c.RenderJSON(response) | ||
} | ||
defer func() { | ||
archiveOpen.Close() | ||
if err := os.Remove(tempArchiveName); err != nil { | ||
logger.Warnf("Unable to remove temp archive: %v", err) | ||
} | ||
}() | ||
|
||
gzipOpen, err := gzip.NewReader(archiveOpen) | ||
if err != nil { | ||
logger.Warnf("Problem with creating stream from temp archive: %v", err) | ||
response := CompileJSONResult(false, "Problem with creating stream from temp archive") | ||
return c.RenderJSON(response) | ||
} | ||
|
||
tarReader := tar.NewReader(gzipOpen) | ||
|
||
for { | ||
header, err := tarReader.Next() | ||
|
||
if err == io.EOF { | ||
break | ||
} | ||
if err != nil { | ||
logger.Warnf("Problem with reading temp archive: %v", err) | ||
response := CompileJSONResult(false, "Problem with reading temp archive") | ||
return c.RenderJSON(response) | ||
} | ||
|
||
switch header.Typeflag { | ||
case tar.TypeDir: | ||
if err := os.Mkdir(localPath+string(filepath.Separator)+header.Name, 0755); err != nil { | ||
logger.Warnf("Cannot create a directory from archive: %v", err) | ||
response := CompileJSONResult(false, "Cannot create a directory from archive") | ||
return c.RenderJSON(response) | ||
} | ||
case tar.TypeReg: | ||
outFile, err := os.Create(localPath + string(filepath.Separator) + header.Name) | ||
if err != nil { | ||
logger.Warnf("Cannot create a file from archive: %v", err) | ||
response := CompileJSONResult(false, "Cannot create a file from archive") | ||
return c.RenderJSON(response) | ||
} | ||
|
||
if _, err := io.Copy(outFile, tarReader); err != nil { | ||
logger.Warnf("Failed to write to a file from archive: %v", err) | ||
response := CompileJSONResult(false, "Failed to write to a file from archive") | ||
return c.RenderJSON(response) | ||
} | ||
outFile.Close() | ||
default: | ||
logger.Warnf("General archive problem: uknown type: %s in %s", header.Typeflag, header.Name) | ||
response := CompileJSONResult(false, "General archive problem, Refer to logs :(") | ||
return c.RenderJSON(response) | ||
} | ||
} | ||
} else { | ||
remoteFileContent, err := SSHsession.Output("cat '" + fileNamePost + "'") | ||
if err != nil { | ||
logger.Warnf("Problem with getting file content: %v", err) | ||
response := CompileJSONResult(false, "Problem with getting file content") | ||
return c.RenderJSON(response) | ||
} | ||
if err := ioutil.WriteFile(localPath+string(filepath.Separator)+fileName, remoteFileContent, 644); err != nil { | ||
logger.Warnf("Problem with writing file content: %v", err) | ||
response := CompileJSONResult(false, "Problem with writing file content") | ||
return c.RenderJSON(response) | ||
} | ||
} | ||
} | ||
response := CompileJSONResult(true, "") | ||
return c.RenderJSON(response) | ||
} |
Oops, something went wrong.