Compare commits

..

No commits in common. "master" and "v1.1.0-stable" have entirely different histories.

18 changed files with 846 additions and 897 deletions

1
.gitignore vendored
View File

@ -22,4 +22,3 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof *.prof
*.DS_Store

View File

@ -1,12 +1,9 @@
package main package main
func correctPath(path string) string { func correctPath(path string) string {
if path[len(path)-1:] == `/` || path[len(path)-1:] == `\` { if path[len(path)-1:] == `/` || path[len(path)-1:] == `\` {
// Case: E.g. remote dir = /myfiles/
return path[:len(path)-1] return path[:len(path)-1]
} else { } else {
// Case: E.g. remote dir = /myfiles
return path return path
} }
} }

32
Main.go
View File

@ -2,21 +2,17 @@ package main
import ( import (
"fmt" "fmt"
"github.com/SommerEngineering/Sync/Sync"
"golang.org/x/crypto/ssh"
"log" "log"
"net"
"os" "os"
"runtime" "runtime"
"time"
"github.com/SommerEngineering/Sync/Sync"
"github.com/howeyc/gopass"
"golang.org/x/crypto/ssh"
) )
func main() { func main() {
// Show the current version: // Show the current version:
log.Println(`Sync v1.3.2`) log.Println(`Sync v1.1.0`)
// Allow Go to use all CPUs: // Allow Go to use all CPUs:
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
@ -27,7 +23,6 @@ func main() {
// Check if the directories are provided: // Check if the directories are provided:
if localDir == `` || remoteDir == `` { if localDir == `` || remoteDir == `` {
log.Println(`Please provide the local and remote directory.`) log.Println(`Please provide the local and remote directory.`)
os.Exit(1)
return return
} }
@ -35,7 +30,6 @@ func main() {
if localDir == `.` { if localDir == `.` {
if currentWD, currentWDError := os.Getwd(); currentWDError != nil { if currentWD, currentWDError := os.Getwd(); currentWDError != nil {
log.Println("Cannot use the current working directory as local directory: " + currentWDError.Error()) log.Println("Cannot use the current working directory as local directory: " + currentWDError.Error())
os.Exit(2)
return return
} else { } else {
log.Println("I use the current working directory as local directory: " + currentWD) log.Println("I use the current working directory as local directory: " + currentWD)
@ -50,12 +44,10 @@ func main() {
// Check if local dir exist // Check if local dir exist
if dirInfo, dirError := os.Stat(localDir); dirError != nil { if dirInfo, dirError := os.Stat(localDir); dirError != nil {
log.Println("There is an error with the local directory: " + dirError.Error()) log.Println("There is an error with the local directory: " + dirError.Error())
os.Exit(3)
return return
} else { } else {
if !dirInfo.IsDir() { if !dirInfo.IsDir() {
log.Println("There is an error with the local directory: You provided a file instead!") log.Println("There is an error with the local directory: You provided a file instead!")
os.Exit(4)
return return
} }
} }
@ -65,13 +57,7 @@ func main() {
if password == `` { if password == `` {
// Promt for the password: // Promt for the password:
fmt.Print(`Please provide the password for the connection: `) fmt.Print(`Please provide the password for the connection: `)
if pass, errPass := gopass.GetPasswd(); errPass != nil { fmt.Scanln(&password)
log.Println(`There was an error reading the password securely: ` + errPass.Error())
os.Exit(5)
return
} else {
password = string(pass)
}
} else { } else {
break break
} }
@ -99,14 +85,12 @@ func main() {
ssh.PasswordCallback(Sync.PasswordCallback), ssh.PasswordCallback(Sync.PasswordCallback),
ssh.KeyboardInteractive(Sync.KeyboardInteractiveChallenge), ssh.KeyboardInteractive(Sync.KeyboardInteractiveChallenge),
}, },
HostKeyCallback: showHostKey(),
} }
// Connect to the SSH server: // Connect to the SSH server:
ssh := Sync.ConnectSSH(config, serverAddrString) ssh := Sync.ConnectSSH(config, serverAddrString)
if ssh == nil { if ssh == nil {
log.Println(`It was not possible to connect to the SSH server.`) log.Println(`It was not possible to connect to the SSH server.`)
os.Exit(6)
return return
} }
@ -114,11 +98,3 @@ func main() {
Sync.Synchronise(ssh, supervised, pushOnly, localDir, remoteDir) Sync.Synchronise(ssh, supervised, pushOnly, localDir, remoteDir)
log.Println("Synchronising done.") log.Println("Synchronising done.")
} }
func showHostKey() ssh.HostKeyCallback {
return func(hostname string, remote net.Addr, key ssh.PublicKey) error {
log.Printf("Your server's hostname is %s (%s) and its public key is %s. If this is wrong, please abort the program now! Wait 16 seconds for your check.", hostname, remote.String(), ssh.FingerprintSHA256(key))
time.Sleep(16 * time.Second)
return nil
}
}

View File

@ -2,24 +2,24 @@
This is a simple SFTP synchronisation tool **without** any external dependencies e.g. PuTTY, WinSCP, OpenSSH. Just download the executable which matches your OS and architecture (32 vs. 64 bits) and run it. This is a simple SFTP synchronisation tool **without** any external dependencies e.g. PuTTY, WinSCP, OpenSSH. Just download the executable which matches your OS and architecture (32 vs. 64 bits) and run it.
## Syntax ## Syntax
Sync uses several parameters: Sync provids a few arguments:
- The `localDir` parameter defines the local directory e.g. `c:\Users\A\My Documents` or `/users/A/My Documents`. If you omit or set it to `.`, the current working directory gets used. - ``localDir`` defines the local directory e.g. ``c:\Users\A\My Documents`` or ``/users/A/My Documents``. If you omit or set it to ``.``, the current working directory gets used.
- The `remoteDir` parameter sets the remote directory e.g. `/users/A/Sync`. - ``remoteDir`` is the remote directory e.g. ``/users/A/Sync``.
- The `server` parameter is the SSH server e.g. `my-server.com:22`. - ``server`` is the SSH server e.g. ``my-server.com:22``.
- Use the `user` parameter to set the user's name and `pwd` to set the user's password. You can omit the `pwd` and the program ask for the password on demand. - Use ``user`` to set the user's name and ``pwd`` to set the user's password. You can omit the ``pwd``, thus, the program ask for the password on demand.
- The parameter `supervised` controls if the program should ask you for every type of change. The default is the supervised operating mode. In order to disable it, use `supervised=false`. - With the argument ``supervised`` you can control if the program should ask you for every type of change. The default is the supervised operating mode. In order to disable it, use ``supervised=false``.
- Finnaly, the `pushOnly` argument can be used to set the **backup mode**, where all remote changes are ignored, which is the default. In order to sync changes in both directions, use `pushOnly=false`. - Finnaly, the ``pushOnly`` argument can be used to set the **backup mode**, where all remote changes are ignored. The default is the enabled backup mode. In order to sync changes in both directions, set ``pushOnly=false``.
## Features ## Features
- The whole code is open source and can be used for any purpose (also commercial) - The whole code is open source and can be used for any purpose (also commercial)
- If you want, you can compile the code by your own by using the [Go](http://www.golang.org) - If you want, you can compile the code by your own by using the [Go](http://www.golang.org)
- The program needs very low resources e.g. around 14 MB of memory on Microsoft Windows 10 to sync roughly 6,000 files. More files need more memory, because the meta information must keep in memory. - The program just needs very low resources e.g. around 14 MB memory for Microsoft Windows 10 to sync roughly 6000 files. More files need more memory, because the meta information must keep in memory.
- If a connection cannot set up, the program re-tries it. - If a connection cannot setup, the program re-tries it
- At the moment, Sync uses only the password authentication methods. Therefore, it is currently not possible to use e.g. a certificate, etc. Nevertheless, the implementation of this feature is possible. - At the moment, SSHTunnel uses only the password authentication methods. Therefore, it is currently not possible to use e.g. a certificate, etc. Nevertheless, the implementation of this feature is possible.
- The configuration must be provided by using the command-line arguments. It is currently not possible to use e.g. a configuration file. - The configuration must be provided by using the command-line arguments. It is currently not possible to use e.g. a configuration file.
- You can avoid the password argument if you prefer to provide the password on demand. - You can avoid the password argument if you prefer to provide the password on demand.
- Sync can be used in **backup mode** where all remote changes are ignored or in the **full mode**, where both directions gets synchronized. - Sync can be used in **backup mode** where all remote changes are ignored or in the **full mode**, where both directions gets synchronised.
- Use the **supervised mode** in order to get the full control about any change. - Use the **supervised mode** in order to get the full control
## Download ## Download
Go and get the latest release from the [release page](https://github.com/SommerEngineering/Sync/releases). Sync use the [SFTP library](https://github.com/pkg/sftp) from Dave Cheney. Thanks very much for the great work! Go and get the latest release from the [release page](https://github.com/SommerEngineering/Sync/releases). Sync use the [SFTP library](https://github.com/pkg/sftp) from Dave Cheney. Thanks very much for the good work!

View File

@ -1,16 +0,0 @@
package Sync
type ByLength []string
func (s ByLength) Len() int {
return len(s)
}
func (s ByLength) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Longer strings will appear first.
func (s ByLength) Less(i, j int) bool {
return len(s[i]) > len(s[j])
}

View File

@ -7,12 +7,6 @@ import (
func normalisePath(base, path string) string { func normalisePath(base, path string) string {
result := strings.Replace(path, base, ``, 1) result := strings.Replace(path, base, ``, 1)
// Ensure that the path starts with a slash:
if len(result) > 1 && result[0:1] != `/` {
result = filepath.Join(`/`, result)
}
result = filepath.ToSlash(result) result = filepath.ToSlash(result)
return strings.ToLower(result) return strings.ToLower(result)
} }

View File

@ -2,6 +2,8 @@ package Sync
import ( import (
"fmt" "fmt"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"io" "io"
"log" "log"
"os" "os"
@ -9,9 +11,6 @@ import (
"sort" "sort"
"strings" "strings"
"time" "time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
) )
func Synchronise(ssh *ssh.Client, supervised, pushOnly bool, localDir, remoteDir string) { func Synchronise(ssh *ssh.Client, supervised, pushOnly bool, localDir, remoteDir string) {
@ -67,7 +66,6 @@ func Synchronise(ssh *ssh.Client, supervised, pushOnly bool, localDir, remoteDir
sftp, sftpError := sftp.NewClient(ssh) sftp, sftpError := sftp.NewClient(ssh)
if sftpError != nil { if sftpError != nil {
log.Println("Was not able to connect to the server: " + sftpError.Error()) log.Println("Was not able to connect to the server: " + sftpError.Error())
os.Exit(7)
return return
} }
@ -157,7 +155,7 @@ func Synchronise(ssh *ssh.Client, supervised, pushOnly bool, localDir, remoteDir
log.Printf("Found %d remote files to delete.\n", len(deleteRemoteFiles)) log.Printf("Found %d remote files to delete.\n", len(deleteRemoteFiles))
if len(deleteRemoteFiles) > 0 { if len(deleteRemoteFiles) > 0 {
sort.Sort(ByLength(deleteRemoteFiles)) sort.Strings(deleteRemoteFiles)
shouldDeleteRemoteFiles := true shouldDeleteRemoteFiles := true
if supervised { if supervised {
fmt.Println(`=================================================================`) fmt.Println(`=================================================================`)
@ -194,6 +192,7 @@ func Synchronise(ssh *ssh.Client, supervised, pushOnly bool, localDir, remoteDir
continue continue
} }
// TODO: Does not work: File an issue!
removeError := sftp.Remove(normalised2remoteFiles[remoteFileNormalised] + `/`) removeError := sftp.Remove(normalised2remoteFiles[remoteFileNormalised] + `/`)
if removeError != nil { if removeError != nil {
log.Printf("Was not able to delete the remote directory %s: %s\n", normalised2remoteFiles[remoteFileNormalised], removeError.Error()) log.Printf("Was not able to delete the remote directory %s: %s\n", normalised2remoteFiles[remoteFileNormalised], removeError.Error())