Compare commits
9 Commits
v1.0.0-sta
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
57dcea32ff | ||
|
a57f82a546 | ||
|
444efe3d31 | ||
|
edfdb47f47 | ||
|
a97c5dadcc | ||
|
71ba42bec8 | ||
|
96183890ac | ||
|
5cd2909677 | ||
|
231e3c69f4 |
49
.gitignore
vendored
49
.gitignore
vendored
@ -1,24 +1,25 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
# Folders
|
# Folders
|
||||||
_obj
|
_obj
|
||||||
_test
|
_test
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
# Architecture specific extensions/prefixes
|
||||||
*.[568vq]
|
*.[568vq]
|
||||||
[568vq].out
|
[568vq].out
|
||||||
|
|
||||||
*.cgo1.go
|
*.cgo1.go
|
||||||
*.cgo2.c
|
*.cgo2.c
|
||||||
_cgo_defun.c
|
_cgo_defun.c
|
||||||
_cgo_gotypes.go
|
_cgo_gotypes.go
|
||||||
_cgo_export.*
|
_cgo_export.*
|
||||||
|
|
||||||
_testmain.go
|
_testmain.go
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
|
*.DS_Store
|
@ -1,9 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
func correctPath(path string) string {
|
func correctPath(path string) string {
|
||||||
if path[len(path)-1:] == `/` || path[len(path)-1:] == `\` {
|
|
||||||
return path[:len(path)-1]
|
if path[len(path)-1:] == `/` || path[len(path)-1:] == `\` {
|
||||||
} else {
|
// Case: E.g. remote dir = /myfiles/
|
||||||
return path
|
return path[:len(path)-1]
|
||||||
}
|
} else {
|
||||||
}
|
// Case: E.g. remote dir = /myfiles
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
48
LICENSE
48
LICENSE
@ -1,24 +1,24 @@
|
|||||||
Copyright (c) 2015, Thorsten Sommer
|
Copyright (c) 2015, Thorsten Sommer
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are met:
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
list of conditions and the following disclaimer.
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
this list of conditions and the following disclaimer in the documentation
|
this list of conditions and the following disclaimer in the documentation
|
||||||
and/or other materials provided with the distribution.
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
224
Main.go
224
Main.go
@ -1,100 +1,124 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/SommerEngineering/Sync/Sync"
|
"log"
|
||||||
"golang.org/x/crypto/ssh"
|
"net"
|
||||||
"log"
|
"os"
|
||||||
"os"
|
"runtime"
|
||||||
"runtime"
|
"time"
|
||||||
)
|
|
||||||
|
"github.com/SommerEngineering/Sync/Sync"
|
||||||
func main() {
|
"github.com/howeyc/gopass"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
// Show the current version:
|
)
|
||||||
log.Println(`Sync v1.0.0`)
|
|
||||||
|
func main() {
|
||||||
// Allow Go to use all CPUs:
|
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
// Show the current version:
|
||||||
|
log.Println(`Sync v1.3.2`)
|
||||||
// Read the configuration from the command-line args:
|
|
||||||
readFlags()
|
// Allow Go to use all CPUs:
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
// Check if the directories are provided:
|
|
||||||
if localDir == `` || remoteDir == `` {
|
// Read the configuration from the command-line args:
|
||||||
log.Println(`Please provide the local and remote directory.`)
|
readFlags()
|
||||||
return
|
|
||||||
}
|
// Check if the directories are provided:
|
||||||
|
if localDir == `` || remoteDir == `` {
|
||||||
// Should I use the current working dir?
|
log.Println(`Please provide the local and remote directory.`)
|
||||||
if localDir == `.` {
|
os.Exit(1)
|
||||||
if currentWD, currentWDError := os.Getwd(); currentWDError != nil {
|
return
|
||||||
log.Println("Cannot use the current working directory as local directory: " + currentWDError.Error())
|
}
|
||||||
return
|
|
||||||
} else {
|
// Should I use the current working dir?
|
||||||
log.Println("I use the current working directory as local directory: " + currentWD)
|
if localDir == `.` {
|
||||||
localDir = currentWD
|
if currentWD, currentWDError := os.Getwd(); currentWDError != nil {
|
||||||
}
|
log.Println("Cannot use the current working directory as local directory: " + currentWDError.Error())
|
||||||
}
|
os.Exit(2)
|
||||||
|
return
|
||||||
// Remove trailing separators from both directories
|
} else {
|
||||||
localDir = correctPath(localDir)
|
log.Println("I use the current working directory as local directory: " + currentWD)
|
||||||
remoteDir = correctPath(remoteDir)
|
localDir = currentWD
|
||||||
|
}
|
||||||
// Check if local dir exist
|
}
|
||||||
if dirInfo, dirError := os.Stat(localDir); dirError != nil {
|
|
||||||
log.Println("There is an error with the local directory: " + dirError.Error())
|
// Remove trailing separators from both directories
|
||||||
return
|
localDir = correctPath(localDir)
|
||||||
} else {
|
remoteDir = correctPath(remoteDir)
|
||||||
if !dirInfo.IsDir() {
|
|
||||||
log.Println("There is an error with the local directory: You provided a file instead!")
|
// Check if local dir exist
|
||||||
return
|
if dirInfo, dirError := os.Stat(localDir); dirError != nil {
|
||||||
}
|
log.Println("There is an error with the local directory: " + dirError.Error())
|
||||||
}
|
os.Exit(3)
|
||||||
|
return
|
||||||
// Check if the password was provided:
|
} else {
|
||||||
for true {
|
if !dirInfo.IsDir() {
|
||||||
if password == `` {
|
log.Println("There is an error with the local directory: You provided a file instead!")
|
||||||
// Promt for the password:
|
os.Exit(4)
|
||||||
fmt.Print(`Please provide the password for the connection: `)
|
return
|
||||||
fmt.Scanln(&password)
|
}
|
||||||
} else {
|
}
|
||||||
break
|
|
||||||
}
|
// Check if the password was provided:
|
||||||
}
|
for true {
|
||||||
|
if password == `` {
|
||||||
// Give some information about the state
|
// Promt for the password:
|
||||||
if supervised {
|
fmt.Print(`Please provide the password for the connection: `)
|
||||||
log.Println("I use the supervised mode.")
|
if pass, errPass := gopass.GetPasswd(); errPass != nil {
|
||||||
} else {
|
log.Println(`There was an error reading the password securely: ` + errPass.Error())
|
||||||
log.Println("I do not use the supervised mode.")
|
os.Exit(5)
|
||||||
}
|
return
|
||||||
|
} else {
|
||||||
if pushOnly {
|
password = string(pass)
|
||||||
log.Println("I use the push only mode i.e. backup mode. Any remote change will be ignored.")
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Println("I use the full mode and consider also remote changes.")
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Create the SSH configuration:
|
|
||||||
Sync.SetPassword4Callback(password)
|
// Give some information about the state
|
||||||
config := &ssh.ClientConfig{
|
if supervised {
|
||||||
User: username,
|
log.Println("I use the supervised mode.")
|
||||||
Auth: []ssh.AuthMethod{
|
} else {
|
||||||
ssh.Password(password),
|
log.Println("I do not use the supervised mode.")
|
||||||
ssh.PasswordCallback(Sync.PasswordCallback),
|
}
|
||||||
ssh.KeyboardInteractive(Sync.KeyboardInteractiveChallenge),
|
|
||||||
},
|
if pushOnly {
|
||||||
}
|
log.Println("I use the push only mode i.e. backup mode. Any remote change will be ignored.")
|
||||||
|
} else {
|
||||||
// Connect to the SSH server:
|
log.Println("I use the full mode and consider also remote changes.")
|
||||||
ssh := Sync.ConnectSSH(config, serverAddrString)
|
}
|
||||||
if ssh == nil {
|
|
||||||
log.Println(`It was not possible to connect to the SSH server.`)
|
// Create the SSH configuration:
|
||||||
return
|
Sync.SetPassword4Callback(password)
|
||||||
}
|
config := &ssh.ClientConfig{
|
||||||
|
User: username,
|
||||||
defer ssh.Close()
|
Auth: []ssh.AuthMethod{
|
||||||
Sync.Synchronise(ssh, supervised, pushOnly, localDir, remoteDir)
|
ssh.Password(password),
|
||||||
log.Println("Synchronising done.")
|
ssh.PasswordCallback(Sync.PasswordCallback),
|
||||||
}
|
ssh.KeyboardInteractive(Sync.KeyboardInteractiveChallenge),
|
||||||
|
},
|
||||||
|
HostKeyCallback: showHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the SSH server:
|
||||||
|
ssh := Sync.ConnectSSH(config, serverAddrString)
|
||||||
|
if ssh == nil {
|
||||||
|
log.Println(`It was not possible to connect to the SSH server.`)
|
||||||
|
os.Exit(6)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer ssh.Close()
|
||||||
|
Sync.Synchronise(ssh, supervised, pushOnly, localDir, remoteDir)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
50
README.md
50
README.md
@ -1,25 +1,25 @@
|
|||||||
# Sync
|
# Sync
|
||||||
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 provids a few arguments:
|
Sync uses several parameters:
|
||||||
- ``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 `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.
|
||||||
- ``remoteDir`` is the remote directory e.g. ``/users/A/Sync``.
|
- The `remoteDir` parameter sets the remote directory e.g. `/users/A/Sync`.
|
||||||
- ``server`` is the SSH server e.g. ``my-server.com:22``.
|
- The `server` parameter is the SSH server e.g. `my-server.com:22`.
|
||||||
- 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.
|
- 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.
|
||||||
- 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``.
|
- 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`.
|
||||||
- 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``.
|
- 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`.
|
||||||
|
|
||||||
## 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 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.
|
- 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.
|
||||||
- If a connection cannot setup, the program re-tries it
|
- If a connection cannot set up, the program re-tries it.
|
||||||
- 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.
|
- 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.
|
||||||
- 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 synchronised.
|
- Sync can be used in **backup mode** where all remote changes are ignored or in the **full mode**, where both directions gets synchronized.
|
||||||
- Use the **supervised mode** in order to get the full control
|
- Use the **supervised mode** in order to get the full control about any change.
|
||||||
|
|
||||||
## 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 good 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 great work!
|
||||||
|
32
ReadFlags.go
32
ReadFlags.go
@ -1,16 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readFlags() {
|
func readFlags() {
|
||||||
flag.StringVar(&serverAddrString, `server`, `127.0.0.1:22`, `The (remote) SSH server, e.g. 'my.host.com', 'my.host.com:22', '127.0.0.1:22', 'localhost:22'.`)
|
flag.StringVar(&serverAddrString, `server`, `127.0.0.1:22`, `The (remote) SSH server, e.g. 'my.host.com', 'my.host.com:22', '127.0.0.1:22', 'localhost:22'.`)
|
||||||
flag.StringVar(&username, `user`, `username`, `The user's name for the SSH server.`)
|
flag.StringVar(&username, `user`, `username`, `The user's name for the SSH server.`)
|
||||||
flag.StringVar(&password, `pwd`, ``, `The user's password for the SSH server. You can omit these argument. Thus, the program asks for the password on demand.`)
|
flag.StringVar(&password, `pwd`, ``, `The user's password for the SSH server. You can omit these argument. Thus, the program asks for the password on demand.`)
|
||||||
flag.StringVar(&localDir, `localDir`, `.`, `The local directory which should be synced. Use . for the current working directory.`)
|
flag.StringVar(&localDir, `localDir`, `.`, `The local directory which should be synced. Use . for the current working directory.`)
|
||||||
flag.StringVar(&remoteDir, `remoteDir`, ``, `The remote directory which should be synced.`)
|
flag.StringVar(&remoteDir, `remoteDir`, ``, `The remote directory which should be synced.`)
|
||||||
flag.BoolVar(&supervised, `supervised`, true, `Use the supervised mode? The algorithm asks you before any change.`)
|
flag.BoolVar(&supervised, `supervised`, true, `Use the supervised mode? The algorithm asks you before any change.`)
|
||||||
flag.BoolVar(&pushOnly, `pushOnly`, true, `Use the push only mode, i.e. backup mode. Ignore any change on the remote side!`)
|
flag.BoolVar(&pushOnly, `pushOnly`, true, `Use the push only mode, i.e. backup mode. Ignore any change on the remote side!`)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
16
Sync/ByLength.go
Normal file
16
Sync/ByLength.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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])
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package Sync
|
package Sync
|
||||||
|
|
||||||
func comparePath(localBase, localPath, remoteBase, remotePath string) bool {
|
func comparePath(localBase, localPath, remoteBase, remotePath string) bool {
|
||||||
localCompare := normalisePath(localBase, localPath)
|
localCompare := normalisePath(localBase, localPath)
|
||||||
remoteCompare := normalisePath(remoteBase, remotePath)
|
remoteCompare := normalisePath(remoteBase, remotePath)
|
||||||
return localCompare == remoteCompare
|
return localCompare == remoteCompare
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,42 @@
|
|||||||
package Sync
|
package Sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConnectSSH(config *ssh.ClientConfig, serverAddrString string) (sshClientConnection *ssh.Client) {
|
func ConnectSSH(config *ssh.ClientConfig, serverAddrString string) (sshClientConnection *ssh.Client) {
|
||||||
|
|
||||||
currentRetriesServer := 0
|
currentRetriesServer := 0
|
||||||
|
|
||||||
// Loop for retries:
|
// Loop for retries:
|
||||||
for {
|
for {
|
||||||
|
|
||||||
// Try to connect to the SSH server:
|
// Try to connect to the SSH server:
|
||||||
if sshClientConn, err := ssh.Dial(`tcp`, serverAddrString, config); err != nil {
|
if sshClientConn, err := ssh.Dial(`tcp`, serverAddrString, config); err != nil {
|
||||||
|
|
||||||
// Failed:
|
// Failed:
|
||||||
currentRetriesServer++
|
currentRetriesServer++
|
||||||
log.Printf("Was not able to connect with the SSH server %s: %s\n", serverAddrString, err.Error())
|
log.Printf("Was not able to connect with the SSH server %s: %s\n", serverAddrString, err.Error())
|
||||||
|
|
||||||
// Is a retry alowed?
|
// Is a retry alowed?
|
||||||
if currentRetriesServer < maxRetriesServer {
|
if currentRetriesServer < maxRetriesServer {
|
||||||
log.Println(`Retry...`)
|
log.Println(`Retry...`)
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// After the return, this thread is closed down. The client can try it again...
|
// After the return, this thread is closed down. The client can try it again...
|
||||||
log.Println(`No more retries for connecting the SSH server.`)
|
log.Println(`No more retries for connecting the SSH server.`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Success:
|
// Success:
|
||||||
log.Println(`Connected to the SSH server ` + serverAddrString)
|
log.Println(`Connected to the SSH server ` + serverAddrString)
|
||||||
sshClientConnection = sshClientConn
|
sshClientConnection = sshClientConn
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
package Sync
|
package Sync
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxRetriesServer = 16 // How many retries are allowed to create the SSH server's connection?
|
maxRetriesServer = 16 // How many retries are allowed to create the SSH server's connection?
|
||||||
)
|
)
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
package Sync
|
package Sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Another auth. method.
|
// Another auth. method.
|
||||||
func KeyboardInteractiveChallenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
|
func KeyboardInteractiveChallenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
|
||||||
|
|
||||||
// Log all the provided data:
|
// Log all the provided data:
|
||||||
log.Println(`User: ` + user)
|
log.Println(`User: ` + user)
|
||||||
log.Println(`Instruction: ` + instruction)
|
log.Println(`Instruction: ` + instruction)
|
||||||
log.Println(`Questions:`)
|
log.Println(`Questions:`)
|
||||||
for q := range questions {
|
for q := range questions {
|
||||||
log.Println(q)
|
log.Println(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
// How many questions are asked?
|
// How many questions are asked?
|
||||||
countQuestions := len(questions)
|
countQuestions := len(questions)
|
||||||
|
|
||||||
if countQuestions == 1 {
|
if countQuestions == 1 {
|
||||||
|
|
||||||
// We expect that in this case (only one question is asked), that the server want to know the password ;-)
|
// We expect that in this case (only one question is asked), that the server want to know the password ;-)
|
||||||
answers = make([]string, countQuestions, countQuestions)
|
answers = make([]string, countQuestions, countQuestions)
|
||||||
answers[0] = callbackPassword
|
answers[0] = callbackPassword
|
||||||
|
|
||||||
} else if countQuestions > 1 {
|
} else if countQuestions > 1 {
|
||||||
|
|
||||||
// After logging, this call will exit the whole program:
|
// After logging, this call will exit the whole program:
|
||||||
log.Fatalln(`The SSH server is asking multiple questions! This program cannot handle this case.`)
|
log.Fatalln(`The SSH server is asking multiple questions! This program cannot handle this case.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = nil
|
err = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
package Sync
|
package Sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func normalisePath(base, path string) string {
|
func normalisePath(base, path string) string {
|
||||||
result := strings.Replace(path, base, ``, 1)
|
result := strings.Replace(path, base, ``, 1)
|
||||||
result = filepath.ToSlash(result)
|
|
||||||
return strings.ToLower(result)
|
// Ensure that the path starts with a slash:
|
||||||
}
|
if len(result) > 1 && result[0:1] != `/` {
|
||||||
|
result = filepath.Join(`/`, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = filepath.ToSlash(result)
|
||||||
|
return strings.ToLower(result)
|
||||||
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package Sync
|
package Sync
|
||||||
|
|
||||||
// Just a callback function for the password request.
|
// Just a callback function for the password request.
|
||||||
func PasswordCallback() (string, error) {
|
func PasswordCallback() (string, error) {
|
||||||
return callbackPassword, nil
|
return callbackPassword, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetPassword4Callback(password string) {
|
func SetPassword4Callback(password string) {
|
||||||
callbackPassword = password
|
callbackPassword = password
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
package Sync
|
package Sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readYesNoAnswer(defaultAnswer bool) bool { // true := yes
|
func readYesNoAnswer(defaultAnswer bool) bool { // true := yes
|
||||||
answer := ``
|
answer := ``
|
||||||
if _, scanError := fmt.Scan(&answer); scanError != nil {
|
if _, scanError := fmt.Scan(&answer); scanError != nil {
|
||||||
return defaultAnswer
|
return defaultAnswer
|
||||||
}
|
}
|
||||||
|
|
||||||
if answer == `` || answer == ` ` {
|
if answer == `` || answer == ` ` {
|
||||||
return defaultAnswer
|
return defaultAnswer
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(answer) == `y` {
|
if strings.ToLower(answer) == `y` {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
1149
Sync/Synchronise.go
1149
Sync/Synchronise.go
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,15 @@
|
|||||||
package Sync
|
package Sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
callbackPassword = ``
|
callbackPassword = ``
|
||||||
localFiles = make(map[string]os.FileInfo) // The local files with local separator
|
localFiles = make(map[string]os.FileInfo) // The local files with local separator
|
||||||
localFilesNormalised = make(map[string]os.FileInfo) // The local files with std separator i.e. / and removed base dir
|
localFilesNormalised = make(map[string]os.FileInfo) // The local files with std separator i.e. / and removed base dir
|
||||||
normalised2localFiles = make(map[string]string) // Mapping from normalised local file to local file
|
normalised2localFiles = make(map[string]string) // Mapping from normalised local file to local file
|
||||||
remoteFiles = make(map[string]os.FileInfo) // The remote files with remote separator
|
remoteFiles = make(map[string]os.FileInfo) // The remote files with remote separator
|
||||||
remoteFilesNormalised = make(map[string]os.FileInfo) // The remote files with std separator i.e. / and removed base dir
|
remoteFilesNormalised = make(map[string]os.FileInfo) // The remote files with std separator i.e. / and removed base dir
|
||||||
normalised2remoteFiles = make(map[string]string) // Mapping from normalised remote file to remote file
|
normalised2remoteFiles = make(map[string]string) // Mapping from normalised remote file to remote file
|
||||||
)
|
)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package Sync
|
package Sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func walkerlocal(path string, info os.FileInfo, err error) error {
|
func walkerlocal(path string, info os.FileInfo, err error) error {
|
||||||
localFiles[path] = info
|
localFiles[path] = info
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
22
Variables.go
22
Variables.go
@ -1,11 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
var (
|
var (
|
||||||
username = `` // The SSH user's name
|
username = `` // The SSH user's name
|
||||||
password = `` // The user's password
|
password = `` // The user's password
|
||||||
serverAddrString = `` // The SSH server address
|
serverAddrString = `` // The SSH server address
|
||||||
localDir = `` // The local directory
|
localDir = `` // The local directory
|
||||||
remoteDir = `` // The remote directory
|
remoteDir = `` // The remote directory
|
||||||
supervised = true // Should the tool work supervised?
|
supervised = true // Should the tool work supervised?
|
||||||
pushOnly = true // Pushes only local changes to the remote i.e. backup mode
|
pushOnly = true // Pushes only local changes to the remote i.e. backup mode
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user