Implement initial SSH data retrieval

This commit is contained in:
2024-05-15 14:58:21 -04:00
parent f86c2d8153
commit 5e83178046
2 changed files with 118 additions and 15 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ func TempInitCli() {
// client device ID // client device ID
deviceIDPrefix, _ := os.Hostname() deviceIDPrefix, _ := os.Hostname()
deviceIDSuffix := backend.StringGen(rand.Intn(48)+48, true, 0.2) deviceIDSuffix := backend.StringGen(rand.Intn(48)+48, true, 0.2)
deviceID := deviceIDPrefix + "-" + deviceIDSuffix // TODO do not save in config file, encrypt in config directory deviceID := deviceIDPrefix + "-" + deviceIDSuffix
// necessary SSH info // necessary SSH info
sshUser := input("Remote SSH username:") sshUser := input("Remote SSH username:")
+117 -14
View File
@@ -3,17 +3,15 @@ package sync
import ( import (
"fmt" "fmt"
"github.com/rwinkhart/MUTN/src/backend" "github.com/rwinkhart/MUTN/src/backend"
"golang.org/x/crypto/ssh"
"os" "os"
"strconv"
"strings"
) )
// returns lists of local entries and mod times (two separate lists) // getSSHOutput runs a command over SSH and returns the output
func getLocalData() { // currently only supports password-less key-based authentication TODO add password support, still require key
fileList, _ := WalkEntryDir() func getSSHOutput(cmd string, manualSync bool) string {
fmt.Println(fileList) // TODO placeholder
}
// RunJob runs the SSH sync job
func RunJob(manualSync bool) {
// get SSH config info, exit if not configured (displaying an error if the sync job was called manually) // get SSH config info, exit if not configured (displaying an error if the sync job was called manually)
var sshUserIPPortIdentity []string var sshUserIPPortIdentity []string
if manualSync { if manualSync {
@@ -22,21 +20,126 @@ func RunJob(manualSync bool) {
sshUserIPPortIdentity = backend.ReadConfig([]string{"sshUser", "sshIP", "sshPort", "sshIdentity"}, "0") sshUserIPPortIdentity = backend.ReadConfig([]string{"sshUser", "sshIP", "sshPort", "sshIdentity"}, "0")
} }
var sshUser, sshIP, sshPort, sshIdentity string var user, ip, port, identity string
for i, key := range sshUserIPPortIdentity { for i, key := range sshUserIPPortIdentity {
switch i { switch i {
case 0: case 0:
sshUser = key user = key
case 1: case 1:
sshIP = key ip = key
case 2: case 2:
sshPort = key port = key
case 3: case 3:
sshIdentity = key identity = key
} }
} }
fmt.Println(sshUser, sshIP, sshPort, sshIdentity) // TODO placeholder // read and parse private key
key, err := os.ReadFile(identity)
if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - unable to read private key file:", identity+backend.AnsiReset)
os.Exit(1)
}
parsedKey, err := ssh.ParsePrivateKey(key)
if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to parse private key:", identity+backend.AnsiReset)
os.Exit(1)
}
// configure SSH client
sshConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(parsedKey),
},
}
sshConfig.HostKeyCallback = ssh.InsecureIgnoreHostKey() // TODO remove
// connect to SSH server
sshClient, err := ssh.Dial("tcp", ip+":"+port, sshConfig)
if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to connect to remote server:", err.Error()+backend.AnsiReset)
os.Exit(1)
}
defer sshClient.Close()
// create a session
sshSession, err := sshClient.NewSession()
if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to establish SSH session:", err.Error()+backend.AnsiReset)
os.Exit(1)
}
// run the provided command
output, err := sshSession.CombinedOutput(cmd)
if err != nil {
fmt.Println(backend.AnsiError+"Sync failed - Unable to run SSH command:", err.Error()+backend.AnsiReset)
os.Exit(1)
}
// convert the output to a string and remove leading/trailing whitespace
outputString := string(output)
outputString = strings.TrimSpace(outputString)
return outputString
}
// getRemoteData returns lists of remote entries, mod times, folders, and deletions (four separate lists)
func getRemoteData(manualSync bool) ([]string, []int64, []string, []string) {
// get remote output over SSH
output := getSSHOutput("ls && uname -r", manualSync) // TODO replace cmd with remote server command (e.g. "libmuttonserver fetch")
// split output into slice based on occurrences of "\x1f"
outputSlice := strings.Split(output, "\x1f")
// re-form the lists
entries := strings.Split(outputSlice[0], "\n")
modsStrings := strings.Split(outputSlice[1], "\n")
folders := strings.Split(outputSlice[2], "\n")
deletions := strings.Split(outputSlice[3], "\n")
// convert the mod times to int64
var mods []int64
for _, modString := range modsStrings {
mod, _ := strconv.ParseInt(modString, 10, 64)
mods = append(mods, mod)
}
return entries, mods, folders, deletions
}
// getLocalData returns lists of local entries and mod times (two separate lists)
func getLocalData() ([]string, []int64) {
// get a list of all entries
fileList, _ := WalkEntryDir()
// get a list of all entry modification times
var modList []int64
for _, file := range fileList {
modTime, _ := os.Stat(backend.EntryRoot + file)
modList = append(modList, modTime.ModTime().Unix())
}
// return the lists
return fileList, modList
}
// RunJob runs the SSH sync job
func RunJob(manualSync bool) {
// TODO fetch remote lists
remoteEntries, remoteMods, remoteFolders, remoteDeletions := getRemoteData(manualSync)
fmt.Println(remoteEntries, remoteMods, remoteFolders, remoteDeletions) // TODO placeholder
// TODO sync deletions and folders
// fetch local lists
localEntries, localMods := getLocalData()
fmt.Println(localEntries, localMods) // TODO placeholder
// TODO sort both local and remote entry/mod lists to ensure common entries are listed first
// TODO sync new and updated entries
// exit program after successful sync
os.Exit(0) os.Exit(0)
} }