From 03c040b373b6b563338d472cb3882f91e82d4c65 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Fri, 9 May 2025 16:12:03 +0000 Subject: [PATCH] Use go-boilerplate --- core/1globals.go | 17 +---- core/2globalsUNIX.go | 8 ++- core/clipClearProcessGeneric.go | 8 ++- core/configParser.go | 11 +-- core/copy.go | 11 +-- core/copyUNIX.go | 8 ++- core/edit.go | 4 +- core/exitHard.go | 10 --- core/exitSoft.go | 8 --- core/init.go | 11 +-- core/launchClipClearProcessCLIUNIX.go | 4 +- core/rcw.go | 13 ++-- core/utilitiesMisc.go | 100 ++------------------------ go.mod | 2 + go.sum | 2 + libmuttonserver.go | 11 +-- sync/client.go | 81 ++++++++++----------- sync/common.go | 17 ++--- sync/commonUNIX.go | 5 +- sync/init.go | 7 +- sync/oneOff.go | 13 ++-- sync/server.go | 3 +- 22 files changed, 130 insertions(+), 224 deletions(-) delete mode 100644 core/exitHard.go delete mode 100644 core/exitSoft.go diff --git a/core/1globals.go b/core/1globals.go index 807cc04..36770b5 100644 --- a/core/1globals.go +++ b/core/1globals.go @@ -1,14 +1,9 @@ package core -import ( - "os" -) - type ByteInputFetcher func(prompt string) []byte var ( - PassphraseInputFunction ByteInputFetcher // Clients should set this to a function that fetches hidden input from the user - Home, _ = os.UserHomeDir() + GetPassphrase ByteInputFetcher // Clients should set this to a function that fetches hidden input from the user ) const ( @@ -18,20 +13,10 @@ const ( FSPath = "\u259e" // ▞ Path separator FSMisc = "\u259f" // ▟ Misc. field separator (if \u259d is already used) - AnsiError = "\033[38;5;9m" - AnsiReset = "\033[0m" - - ErrorRead = 101 - ErrorWrite = 102 ErrorSyncProcess = 103 ErrorServerConnection = 104 - ErrorTargetNotFound = 105 ErrorTargetExists = 106 - ErrorTargetWrongType = 107 ErrorDecryption = 108 ErrorEncryption = 109 ErrorClipboard = 110 - ErrorOther = 111 ) - -var GetPassphrase func() []byte diff --git a/core/2globalsUNIX.go b/core/2globalsUNIX.go index d4b1059..5ea98a2 100644 --- a/core/2globalsUNIX.go +++ b/core/2globalsUNIX.go @@ -2,10 +2,12 @@ package core +import "github.com/rwinkhart/go-boilerplate/back" + var ( - EntryRoot = Home + "/.local/share/libmutton" // Path to libmutton entry directory - ConfigDir = Home + "/.config/libmutton" // Path to libmutton configuration directory - ConfigPath = ConfigDir + "/libmutton.ini" // Path to libmutton configuration file + EntryRoot = back.Home + "/.local/share/libmutton" // Path to libmutton entry directory + ConfigDir = back.Home + "/.config/libmutton" // Path to libmutton configuration directory + ConfigPath = ConfigDir + "/libmutton.ini" // Path to libmutton configuration file ) const ( diff --git a/core/clipClearProcessGeneric.go b/core/clipClearProcessGeneric.go index 7580a21..5cd81ce 100644 --- a/core/clipClearProcessGeneric.go +++ b/core/clipClearProcessGeneric.go @@ -5,6 +5,8 @@ package core import ( "strings" "time" + + "github.com/rwinkhart/go-boilerplate/back" ) // clipClearProcess clears the clipboard after 30 seconds if the clipboard contents have not changed. @@ -15,9 +17,9 @@ func clipClearProcess(assignedContents string) { clearClipboard := func() { err := cmdClear.Run() if err != nil { - PrintError("Failed to clear clipboard", ErrorClipboard, true) + back.PrintError("Failed to clear clipboard", ErrorClipboard, true) } - Exit(0) + back.Exit(0) } // if assignedContents is empty, clear the clipboard immediately and unconditionally @@ -31,7 +33,7 @@ func clipClearProcess(assignedContents string) { newContents, err := cmdPaste.Output() if err != nil { - PrintError("Failed to read clipboard contents", ErrorClipboard, true) + back.PrintError("Failed to read clipboard contents", ErrorClipboard, true) } if assignedContents == strings.TrimRight(string(newContents), "\r\n") { diff --git a/core/configParser.go b/core/configParser.go index a8dc50e..f232da0 100644 --- a/core/configParser.go +++ b/core/configParser.go @@ -5,6 +5,7 @@ import ( "io/fs" "os" + "github.com/rwinkhart/go-boilerplate/back" "gopkg.in/ini.v1" ) @@ -13,7 +14,7 @@ import ( func loadConfig() *ini.File { cfg, err := ini.Load(ConfigPath) if err != nil { - PrintError("Failed to load libmutton.ini: "+err.Error(), ErrorRead, true) + back.PrintError("Failed to load libmutton.ini: "+err.Error(), back.ErrorRead, true) } return cfg } @@ -38,11 +39,11 @@ func ParseConfig(valuesRequested [][2]string, missingValueError string) ([]strin case "": err = fmt.Errorf("Failed to find value for key \"%s\" in section \"[%s]\" in libmutton.ini", pair[1], pair[0]) case "0": - Exit(0) // hard (expected) exit for CLI; GUI/TUI continue silently + back.Exit(0) // hard (expected) exit for CLI; GUI/TUI continue silently default: err = fmt.Errorf("%s", missingValueError) } - PrintError(err.Error(), ErrorRead, false) + back.PrintError(err.Error(), back.ErrorRead, false) // if interactive (soft exit), return nil and the error to be handled by the caller return nil, err } @@ -60,7 +61,7 @@ func GenDeviceIDList(errorOnFail bool) *[]fs.DirEntry { deviceIDList, err := os.ReadDir(ConfigDir + PathSeparator + "devices") if err != nil { if errorOnFail { - PrintError("Failed to read the devices directory: "+err.Error(), ErrorRead, true) + back.PrintError("Failed to read the devices directory: "+err.Error(), back.ErrorRead, true) } else { return nil // a nil return value indicates that the devices directory could not be read/does not exist } @@ -109,6 +110,6 @@ func WriteConfig(valuesToWrite [][3]string, keysToPrune [][2]string, append bool // save to libmutton.ini err := cfg.SaveTo(ConfigPath) if err != nil { - PrintError("Failed to save libmutton.ini: "+err.Error(), ErrorWrite, true) + back.PrintError("Failed to save libmutton.ini: "+err.Error(), back.ErrorWrite, true) } } diff --git a/core/copy.go b/core/copy.go index 421c116..8e3685a 100644 --- a/core/copy.go +++ b/core/copy.go @@ -8,11 +8,12 @@ import ( steamtotp "github.com/fortis/go-steam-totp" "github.com/pquerna/otp/totp" + "github.com/rwinkhart/go-boilerplate/back" ) // CopyArgument copies a field from an entry to the clipboard. func CopyArgument(targetLocation string, field int) { - if isFile, _ := TargetIsFile(targetLocation, true, 2); isFile { + if isFile, _ := back.TargetIsFile(targetLocation, true, 2); isFile { decryptedEntry := DecryptFileToSlice(targetLocation) var copySubject string // will store data to be copied @@ -22,7 +23,7 @@ func CopyArgument(targetLocation string, field int) { // ensure field is not empty if decryptedEntry[field] == "" { - PrintError("Field is empty", ErrorTargetNotFound, true) + back.PrintError("Field is empty", back.ErrorTargetNotFound, true) } if field != 2 { @@ -48,7 +49,7 @@ func CopyArgument(targetLocation string, field int) { } } } else { - PrintError("Field does not exist in entry", ErrorTargetNotFound, true) + back.PrintError("Field does not exist in entry", back.ErrorTargetNotFound, true) } // copy field to clipboard, launch clipboard clearing process @@ -58,7 +59,7 @@ func CopyArgument(targetLocation string, field int) { // ClipClearArgument reads the assigned clipboard contents from stdin and passes them to clipClearProcess. func ClipClearArgument() { - assignedContents := readFromStdin() + assignedContents := back.ReadFromStdin() if assignedContents == "" { os.Exit(0) // use os.Exit instead of core.Exit, as this function runs out of a background subprocess that is invisible to the user (will never appear in GUI/TUI environment) } @@ -77,7 +78,7 @@ func GenTOTP(secret string, time time.Time, forSteam bool) string { } if err != nil { - PrintError("Error generating TOTP code", ErrorOther, true) + back.PrintError("Error generating TOTP code", back.ErrorOther, true) } return totpToken diff --git a/core/copyUNIX.go b/core/copyUNIX.go index ff1099c..6cd5231 100644 --- a/core/copyUNIX.go +++ b/core/copyUNIX.go @@ -5,6 +5,8 @@ package core import ( "os" "os/exec" + + "github.com/rwinkhart/go-boilerplate/back" ) // copyString copies a string to the clipboard. @@ -18,13 +20,13 @@ func copyString(continuous bool, copySubject string) { } else if _, envSet = os.LookupEnv("DISPLAY"); envSet { cmdCopy = exec.Command("xclip", "-sel", "c", "-t", "text/plain") } else { - PrintError("Clipboard platform could not be determined", ErrorClipboard, true) + back.PrintError("Clipboard platform could not be determined", ErrorClipboard, true) } - writeToStdin(cmdCopy, copySubject) + back.WriteToStdin(cmdCopy, copySubject) err := cmdCopy.Run() if err != nil { - PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true) + back.PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true) } if !continuous { diff --git a/core/edit.go b/core/edit.go index 9f3c863..1b9bf4b 100644 --- a/core/edit.go +++ b/core/edit.go @@ -1,9 +1,11 @@ package core +import "github.com/rwinkhart/go-boilerplate/back" + // GetOldEntryData decrypts and returns old entry data (with all required lines present). func GetOldEntryData(targetLocation string, field int) []string { // ensure targetLocation exists - TargetIsFile(targetLocation, true, 2) + back.TargetIsFile(targetLocation, true, 2) // read old entry data unencryptedEntry := DecryptFileToSlice(targetLocation) diff --git a/core/exitHard.go b/core/exitHard.go deleted file mode 100644 index 2a2b431..0000000 --- a/core/exitHard.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !interactive - -package core - -import "os" - -// Exit (hard) is meant to be used in non-interactive CLI implementations to exit the program after an operation. -func Exit(code int) { - os.Exit(code) -} diff --git a/core/exitSoft.go b/core/exitSoft.go deleted file mode 100644 index f2d3af0..0000000 --- a/core/exitSoft.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build interactive - -package core - -// Exit (soft) is meant to be used in interactive implementations (GUIs/TUIs) to keep the program running after an operation. -func Exit(code int) int { - return code -} diff --git a/core/init.go b/core/init.go index 7578362..a79d8d0 100644 --- a/core/init.go +++ b/core/init.go @@ -3,6 +3,7 @@ package core import ( "os" + "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/rcw/wrappers" ) @@ -10,7 +11,7 @@ import ( func RCWSanityCheckGen(passphrase []byte) { err := wrappers.GenSanityCheck(ConfigDir+PathSeparator+"sanity.rcw", passphrase) if err != nil { - PrintError("Failed to generate sanity check file: "+err.Error(), ErrorWrite, true) + back.PrintError("Failed to generate sanity check file: "+err.Error(), back.ErrorWrite, true) } } @@ -20,7 +21,7 @@ func DirInit(preserveOldConfigDir bool) string { // create EntryRoot err := os.MkdirAll(EntryRoot, 0700) if err != nil { - PrintError("Failed to create \""+EntryRoot+"\": "+err.Error(), ErrorWrite, true) + back.PrintError("Failed to create \""+EntryRoot+"\": "+err.Error(), back.ErrorWrite, true) } // get old device ID before its potential removal @@ -28,11 +29,11 @@ func DirInit(preserveOldConfigDir bool) string { // remove existing config directory (if it exists and not in append mode) if !preserveOldConfigDir { - _, isAccessible := TargetIsFile(ConfigDir, false, 1) + _, isAccessible := back.TargetIsFile(ConfigDir, false, 1) if isAccessible { err = os.RemoveAll(ConfigDir) if err != nil { - PrintError("Failed to remove existing config directory: "+err.Error(), ErrorWrite, true) + back.PrintError("Failed to remove existing config directory: "+err.Error(), back.ErrorWrite, true) } } } @@ -40,7 +41,7 @@ func DirInit(preserveOldConfigDir bool) string { // create config directory w/devices subdirectory err = os.MkdirAll(ConfigDir+PathSeparator+"devices", 0700) if err != nil { - PrintError("Failed to create \""+ConfigDir+"\": "+err.Error(), ErrorWrite, true) + back.PrintError("Failed to create \""+ConfigDir+"\": "+err.Error(), back.ErrorWrite, true) } return oldDeviceID diff --git a/core/launchClipClearProcessCLIUNIX.go b/core/launchClipClearProcessCLIUNIX.go index 3fdfb0a..0f9d118 100644 --- a/core/launchClipClearProcessCLIUNIX.go +++ b/core/launchClipClearProcessCLIUNIX.go @@ -6,13 +6,15 @@ import ( "os" "os/exec" "strconv" + + "github.com/rwinkhart/go-boilerplate/back" ) // LaunchClipClearProcess launches the timed clipboard clearing process. // For non-interactive CLI implementations, an entirely separate process is created for this purpose. func LaunchClipClearProcess(copySubject string, isWayland bool) { cmd := exec.Command(os.Args[0], "clipclear", strconv.FormatBool(isWayland)) - writeToStdin(cmd, copySubject) + back.WriteToStdin(cmd, copySubject) cmd.Start() os.Exit(0) // use os.Exit directly since this version of this function is only meant for non-interactive CLI implementations } diff --git a/core/rcw.go b/core/rcw.go index 72fef2b..05da23c 100644 --- a/core/rcw.go +++ b/core/rcw.go @@ -6,13 +6,14 @@ import ( "os/exec" "strings" + "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/rcw/daemon" "github.com/rwinkhart/rcw/wrappers" ) // RCWDArgument reads the passphrase from stdin and caches it via an RCW daemon. func RCWDArgument() { - passphrase := readFromStdin() + passphrase := back.ReadFromStdin() if passphrase == "" { os.Exit(0) } @@ -24,7 +25,7 @@ func DecryptFileToSlice(targetLocation string) []string { // read encrypted file encBytes, err := os.ReadFile(targetLocation) if err != nil { - PrintError("Failed to open \""+targetLocation+"\" for decryption - "+err.Error(), ErrorDecryption, true) + back.PrintError("Failed to open \""+targetLocation+"\" for decryption - "+err.Error(), back.ErrorRead, true) } // decrypt data using RCW daemon @@ -37,7 +38,7 @@ func DecryptFileToSlice(targetLocation string) []string { // directly to avoid waiting for socket file creation decBytes, err := wrappers.Decrypt(encBytes, passphrase) if err != nil { - PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true) + back.PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true) } return strings.Split(string(decBytes), "\n") } @@ -62,15 +63,15 @@ func launchRCWDProcess() []byte { } var passphrase []byte for { - passphrase = GetPassphrase() + passphrase = GetPassphrase("RCW Passphrase:") err := wrappers.RunSanityCheck(ConfigDir+PathSeparator+"sanity.rcw", passphrase) if err == nil { break } - fmt.Println(AnsiError + "Incorrect passphrase" + AnsiReset) + fmt.Println(back.AnsiError + "Incorrect passphrase" + back.AnsiReset) } cmd := exec.Command(os.Args[0], "startrcwd") - writeToStdin(cmd, string(passphrase)) + back.WriteToStdin(cmd, string(passphrase)) cmd.Start() return passphrase diff --git a/core/utilitiesMisc.go b/core/utilitiesMisc.go index 46c2fcb..4f1cc9d 100644 --- a/core/utilitiesMisc.go +++ b/core/utilitiesMisc.go @@ -1,93 +1,25 @@ package core import ( - "bufio" "crypto/rand" "fmt" - "io" "math" "math/big" "os" - "os/exec" "strings" -) -// TargetIsFile checks if the targetLocation is a file, directory, or is inaccessible. -// Requires: failCondition (0 = fail on inaccessible, 1 = fail on inaccessible&file, 2 = fail on inaccessible&directory). -// Returns: isFile, isAccessible. -func TargetIsFile(targetLocation string, errorOnFail bool, failCondition uint8) (bool, bool) { - targetInfo, err := os.Stat(targetLocation) - if err != nil { - if errorOnFail { - PrintError("Failed to access \""+targetLocation+"\" - Ensure it exists and has the correct permissions", ErrorTargetNotFound, true) - } - return false, false - } - if targetInfo.IsDir() { - if errorOnFail && failCondition == 2 { - PrintError("\""+targetLocation+"\" is a directory", ErrorTargetWrongType, true) - } - return false, true - } else { - if errorOnFail && failCondition == 1 { - PrintError("\""+targetLocation+"\" is a file", ErrorTargetWrongType, true) - } - return true, true - } -} + "github.com/rwinkhart/go-boilerplate/back" +) // WriteEntry writes entryData to an encrypted file at targetLocation. func WriteEntry(targetLocation string, entryData []byte) { encryptedBytes := EncryptBytes(entryData) err := os.WriteFile(targetLocation, encryptedBytes, 0600) if err != nil { - PrintError("Failed to write to file: "+err.Error(), ErrorWrite, true) + back.PrintError("Failed to write to file: "+err.Error(), back.ErrorWrite, true) } } -// writeToStdin is a utility function that writes a string to a command's stdin. -func writeToStdin(cmd *exec.Cmd, input string) { - stdin, err := cmd.StdinPipe() - if err != nil { - PrintError("Failed to access stdin for system command: "+err.Error(), ErrorOther, true) - } - - go func() { - defer func(stdin io.WriteCloser) { - _ = stdin.Close() // error ignored; if stdin could be accessed, it can probably be closed - }(stdin) - _, _ = io.WriteString(stdin, input) - }() -} - -// readFromStdin is a utility function that reads a string from stdin. -func readFromStdin() string { - scanner := bufio.NewScanner(os.Stdin) - if scanner.Scan() { - return scanner.Text() - } - return "" -} - -// CreateTempFile creates a temporary file and returns a pointer to it. -func CreateTempFile() *os.File { - tempFile, err := os.CreateTemp("", "*.markdown") - if err != nil { - PrintError("Failed to create temporary file: "+err.Error(), ErrorWrite, true) - } - return tempFile -} - -// RemoveTrailingEmptyStrings removes empty strings from the end of a slice. -func RemoveTrailingEmptyStrings(slice []string) []string { - for i := len(slice) - 1; i >= 0; i-- { - if slice[i] != "" { - return slice[:i+1] - } - } - return nil -} - // ClampTrailingWhitespace strips trailing newlines, carriage returns, and tabs from each line in a note. // Additionally, it removes single trailing spaces and truncates multiple trailing spaces to two (for Markdown formatting). func ClampTrailingWhitespace(note []string) { @@ -123,16 +55,16 @@ func ClampTrailingWhitespace(note []string) { // Returns: statusCode (0 = success, 1 = target location already exists, 2 = containing directory is invalid). func EntryAddPrecheck(targetLocation string) uint8 { // ensure target location does not already exist - _, isAccessible := TargetIsFile(targetLocation, false, 0) + _, isAccessible := back.TargetIsFile(targetLocation, false, 0) if isAccessible { - PrintError("Target location already exists", ErrorTargetExists, false) + back.PrintError("Target location already exists", ErrorTargetExists, false) return 1 // inform interactive clients that the target location already exists } // ensure target containing directory exists and is a directory (not a file) containingDir := targetLocation[:strings.LastIndex(targetLocation, PathSeparator)] - isFile, isAccisAccessible := TargetIsFile(containingDir, false, 1) + isFile, isAccisAccessible := back.TargetIsFile(containingDir, false, 1) if isFile || !isAccisAccessible { - PrintError("\""+containingDir+"\" is not a valid containing directory", ErrorTargetWrongType, false) + back.PrintError("\""+containingDir+"\" is not a valid containing directory", back.ErrorTargetWrongType, false) return 2 // inform interactive clients that the containing directory is invalid } return 0 @@ -205,21 +137,3 @@ func EntryIsNotEmpty(entryData []string) bool { } return false } - -// ExpandPathWithHome, given a path (as a string) containing "~", returns the path with "~" expanded to the user's home directory. -func ExpandPathWithHome(path string) string { - return strings.Replace(path, "~", Home, 1) -} - -// PrintError prints an error message in the standard libmutton format and exits with the specified exit code. -// Requires: message (the error message to print), -// exitCode (the exit code to use), -// forceHardExit (if true, exit immediately; if false, allow soft exit for interactive clients). -func PrintError(message string, exitCode int, forceHardExit bool) { - fmt.Println(AnsiError + message + AnsiReset) - if forceHardExit { - os.Exit(exitCode) - } else { - Exit(exitCode) - } -} diff --git a/go.mod b/go.mod index 5ccaa7a..f3710de 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,8 @@ require ( golang.org/x/sys v0.33.0 // indirect ) +require github.com/rwinkhart/go-boilerplate v0.0.0-20250509154735-0846290a7620 + replace golang.org/x/sys => github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0 replace github.com/Microsoft/go-winio => github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410 diff --git a/go.sum b/go.sum index 66f931d..11caa67 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481 h1:FkxbO331O7mS5EJkP+MCi0o2gswh/Aezs+//NmefrR8= github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/rwinkhart/go-boilerplate v0.0.0-20250509154735-0846290a7620 h1:MjxVq+EqJgB/sxgSbPTTuzbOrU61kKa4oJvjqqdEoO4= +github.com/rwinkhart/go-boilerplate v0.0.0-20250509154735-0846290a7620/go.mod h1:cnzIF45I0FCOvE4YIB+26pLCUx2kWyY2llKYZruNaRY= github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410 h1:NhHwFM3Pgm6zRUfFKvi0p5ndjfFbVWsRwmmhyFlG4PE= github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/rwinkhart/peercred-mini v0.0.0-20250407033241-c09add2eceea h1:VE2ti/AE4Y3kgnyK+J5c5hpddE+dKHpsFch3K2R6puQ= diff --git a/libmuttonserver.go b/libmuttonserver.go index dc2efac..7a90054 100644 --- a/libmuttonserver.go +++ b/libmuttonserver.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/core" "github.com/rwinkhart/libmutton/sync" ) @@ -77,13 +78,13 @@ func main() { } func helpServer() { - fmt.Print(ansiBold + "\nlibmuttonserver | Copyright (c) 2024-2025 Randall Winkhart\n" + core.AnsiReset + ` + fmt.Print(ansiBold + "\nlibmuttonserver | Copyright (c) 2024-2025 Randall Winkhart\n" + back.AnsiReset + ` This software exists under the MIT license; you may redistribute it under certain conditions. This program comes with absolutely no warranty; type "libmuttonserver version" for details. -` + ansiBold + "Usage:" + core.AnsiReset + ` libmuttonserver +` + ansiBold + "Usage:" + back.AnsiReset + ` libmuttonserver -` + ansiBold + "Arguments (user):" + core.AnsiReset + ` +` + ansiBold + "Arguments (user):" + back.AnsiReset + ` help Bring up this menu version Display version and license information init Create the necessary directories for libmuttonserver to function` + "\n\n") @@ -91,7 +92,7 @@ This program comes with absolutely no warranty; type "libmuttonserver version" f } func versionServer() { - fmt.Print(ansiBold + "\n MIT License" + core.AnsiReset + ` + fmt.Print(ansiBold + "\n MIT License" + back.AnsiReset + ` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated @@ -116,5 +117,5 @@ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.` + "\n\n---------------------------------------------------------") - fmt.Print(ansiBold + "\n\n libmuttonserver" + core.AnsiReset + " Version " + core.LibmuttonVersion + "\n\n Copyright (c) 2024-2025: Randall Winkhart" + "\n\n") + fmt.Print(ansiBold + "\n\n libmuttonserver" + back.AnsiReset + " Version " + core.LibmuttonVersion + "\n\n Copyright (c) 2024-2025: Randall Winkhart" + "\n\n") } diff --git a/sync/client.go b/sync/client.go index cd70542..d7df6e0 100644 --- a/sync/client.go +++ b/sync/client.go @@ -8,6 +8,7 @@ import ( "time" "github.com/pkg/sftp" + "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/core" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" @@ -53,7 +54,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { case 6: isWindows, err = strconv.ParseBool(key) if err != nil { - core.PrintError("Sync failed - Unable to parse server OS type: "+err.Error(), core.ErrorRead, true) + back.PrintError("Sync failed - Unable to parse server OS type: "+err.Error(), back.ErrorRead, true) } } } @@ -61,7 +62,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { // read private key key, err := os.ReadFile(keyFile) if err != nil { - core.PrintError("Sync failed - Unable to read private key: "+keyFile, core.ErrorRead, true) + back.PrintError("Sync failed - Unable to read private key: "+keyFile, back.ErrorRead, true) } // parse private key @@ -69,17 +70,17 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { if keyFileProtected != "true" { parsedKey, err = ssh.ParsePrivateKey(key) } else { - parsedKey, err = ssh.ParsePrivateKeyWithPassphrase(key, core.PassphraseInputFunction("Enter passphrase for your SSH keyfile:")) + parsedKey, err = ssh.ParsePrivateKeyWithPassphrase(key, core.GetPassphrase("Enter passphrase for your SSH keyfile:")) } if err != nil { - core.PrintError("Sync failed - Unable to parse private key: "+keyFile, core.ErrorRead, true) + back.PrintError("Sync failed - Unable to parse private key: "+keyFile, back.ErrorRead, true) } // read known hosts file var hostKeyCallback ssh.HostKeyCallback - hostKeyCallback, err = knownhosts.New(core.Home + core.PathSeparator + ".ssh" + core.PathSeparator + "known_hosts") + hostKeyCallback, err = knownhosts.New(back.Home + core.PathSeparator + ".ssh" + core.PathSeparator + "known_hosts") if err != nil { - core.PrintError("Sync failed - Unable to read known hosts file: "+err.Error(), core.ErrorRead, true) + back.PrintError("Sync failed - Unable to read known hosts file: "+err.Error(), back.ErrorRead, true) } // configure SSH client @@ -95,7 +96,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { // connect to SSH server sshClient, err := ssh.Dial("tcp", ip+":"+port, sshConfig) if err != nil { - core.PrintError("Sync failed - Unable to connect to remote server: "+err.Error(), core.ErrorServerConnection, false) // do not crash/close interactive clients + back.PrintError("Sync failed - Unable to connect to remote server: "+err.Error(), core.ErrorServerConnection, false) // do not crash/close interactive clients return nil, "", false } @@ -107,7 +108,7 @@ func GetSSHOutput(sshClient *ssh.Client, cmd, stdin string) string { // create a session sshSession, err := sshClient.NewSession() if err != nil { - core.PrintError("Sync failed - Unable to establish SSH session: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to establish SSH session: "+err.Error(), core.ErrorServerConnection, true) } // provide stdin data for session @@ -117,7 +118,7 @@ func GetSSHOutput(sshClient *ssh.Client, cmd, stdin string) string { var output []byte output, err = sshSession.CombinedOutput(cmd) if err != nil { - core.PrintError("Sync failed - Unable to run SSH command: "+err.Error(), core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to run SSH command: "+err.Error(), core.ErrorSyncProcess, true) } // convert the output to a string and remove leading/trailing whitespace @@ -133,9 +134,9 @@ func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string deviceIDList := core.GenDeviceIDList(true) if len(*deviceIDList) == 0 { if manualSync { - core.PrintError("Sync failed - No device ID found", core.ErrorTargetNotFound, true) + back.PrintError("Sync failed - No device ID found", back.ErrorTargetNotFound, true) } else { - core.Exit(0) // exit silently if the sync job was called automatically, as the user may just be in offline mode + back.Exit(0) // exit silently if the sync job was called automatically, as the user may just be in offline mode } } clientTime := time.Now().Unix() // get client time now to avoid accuracy issues caused by unpredictable sync time @@ -146,11 +147,11 @@ func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string // parse output/re-form lists if len(outputSlice) != 5 { // ensure information from server is complete - core.PrintError("Sync failed - Unable to fetch remote data; server returned an unexpected response", core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to fetch remote data; server returned an unexpected response", core.ErrorSyncProcess, true) } serverTime, err := strconv.ParseInt(outputSlice[0], 10, 64) if err != nil { - core.PrintError("Sync failed - Unable to parse server time: "+err.Error(), core.ErrorRead, true) + back.PrintError("Sync failed - Unable to parse server time: "+err.Error(), back.ErrorRead, true) } entries := strings.Split(outputSlice[1], core.FSMisc)[1:] modsStrings := strings.Split(outputSlice[2], core.FSMisc)[1:] @@ -163,7 +164,7 @@ func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string for _, modString := range modsStrings { mod, err = strconv.ParseInt(modString, 10, 64) if err != nil { - core.PrintError("Sync failed - Unable to parse mod time: "+err.Error(), core.ErrorRead, true) + back.PrintError("Sync failed - Unable to parse mod time: "+err.Error(), back.ErrorRead, true) } mods = append(mods, mod) } @@ -209,12 +210,12 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow // create an SFTP client from sshClient sftpClient, err := sftp.NewClient(sshClient) if err != nil { - core.PrintError("Sync failed - Unable to establish SFTP session: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to establish SFTP session: "+err.Error(), core.ErrorServerConnection, true) } defer func(sftpClient *sftp.Client) { err = sftpClient.Close() if err != nil { - core.PrintError("Sync failed - Unable to close SFTP client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SFTP client: "+err.Error(), core.ErrorServerConnection, true) } }(sftpClient) @@ -223,7 +224,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow for _, entryName := range downloadList { filesTransferred = true // set a flag to indicate that files have been downloaded (used to determine whether to print a gap between download and upload messages) - fmt.Println("Downloading " + ansiDownload + entryName + core.AnsiReset) + fmt.Println("Downloading " + ansiDownload + entryName + back.AnsiReset) // store path to remote entry remoteEntryFullPath := targetLocationFormatSFTP(entryName, sshEntryRoot, sshIsWindows) @@ -232,7 +233,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow var fileInfo os.FileInfo fileInfo, err = sftpClient.Stat(remoteEntryFullPath) if err != nil { - core.PrintError("Sync failed - Unable to get remote file info (mod time): "+err.Error(), core.ErrorRead, true) + back.PrintError("Sync failed - Unable to get remote file info (mod time): "+err.Error(), back.ErrorRead, true) } modTime := fileInfo.ModTime() @@ -240,7 +241,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow var remoteFile *sftp.File remoteFile, err = sftpClient.Open(remoteEntryFullPath) if err != nil { - core.PrintError("Sync failed - Unable to open remote file: "+err.Error(), core.ErrorRead, true) + back.PrintError("Sync failed - Unable to open remote file: "+err.Error(), back.ErrorRead, true) } // store path to local entry @@ -250,13 +251,13 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow var localFile *os.File localFile, err = os.OpenFile(localEntryFullPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - core.PrintError("Sync failed - Unable to create local file: "+err.Error(), core.ErrorWrite, true) + back.PrintError("Sync failed - Unable to create local file: "+err.Error(), back.ErrorWrite, true) } // download the file _, err = remoteFile.WriteTo(localFile) if err != nil { - core.PrintError("Sync failed - Unable to download remote file: "+err.Error(), core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to download remote file: "+err.Error(), core.ErrorSyncProcess, true) } // close the files @@ -276,7 +277,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow for _, entryName := range uploadList { filesTransferred = true // set a flag to indicate that files have been uploaded (used to determine whether to print a gap between upload and sync complete messages) - fmt.Println("Uploading " + ansiUpload + entryName + core.AnsiReset) + fmt.Println("Uploading " + ansiUpload + entryName + back.AnsiReset) // store path to local entry localEntryFullPath := core.TargetLocationFormat(entryName) @@ -285,7 +286,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow var fileInfo os.FileInfo fileInfo, err = os.Stat(localEntryFullPath) if err != nil { - core.PrintError("Sync failed - Unable to get local file info (mod time): "+err.Error(), core.ErrorRead, true) + back.PrintError("Sync failed - Unable to get local file info (mod time): "+err.Error(), back.ErrorRead, true) } modTime := fileInfo.ModTime() @@ -293,7 +294,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow var localFile *os.File localFile, err = os.Open(localEntryFullPath) if err != nil { - core.PrintError("Sync failed - Unable to open local file: "+err.Error(), core.ErrorRead, true) + back.PrintError("Sync failed - Unable to open local file: "+err.Error(), back.ErrorRead, true) } // store path to remote entry @@ -303,13 +304,13 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow var remoteFile *sftp.File remoteFile, err = sftpClient.OpenFile(remoteEntryFullPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY) if err != nil { - core.PrintError("Sync failed - Unable to create remote file ("+remoteEntryFullPath+"): "+err.Error(), core.ErrorWrite, true) + back.PrintError("Sync failed - Unable to create remote file ("+remoteEntryFullPath+"): "+err.Error(), back.ErrorWrite, true) } // upload the file _, err = localFile.WriteTo(remoteFile) if err != nil { - core.PrintError("Sync failed - Unable to upload local file: "+err.Error(), core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to upload local file: "+err.Error(), core.ErrorSyncProcess, true) } // close the files @@ -319,7 +320,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow // set permissions on remote file err = sftpClient.Chmod(remoteEntryFullPath, 0600) if err != nil { - core.PrintError("Sync failed - Unable to set permissions on remote file: "+err.Error(), core.ErrorSyncProcess, true) + back.PrintError("Sync failed - Unable to set permissions on remote file: "+err.Error(), core.ErrorSyncProcess, true) } // set the modification time of the remote file to match the value saved from the local file (from before the upload) @@ -343,23 +344,23 @@ func syncLists(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows, timeSyn if remoteModTime, present := remoteEntryModMap[entry]; present { // entry exists on both client and server, compare mod times if remoteModTime > localModTime { - fmt.Println(ansiDownload+entry+core.AnsiReset, "is newer on server, adding to download list") + fmt.Println(ansiDownload+entry+back.AnsiReset, "is newer on server, adding to download list") downloadList = append(downloadList, entry) } else if remoteModTime < localModTime { - fmt.Println(ansiUpload+entry+core.AnsiReset, "is newer on client, adding to upload list") + fmt.Println(ansiUpload+entry+back.AnsiReset, "is newer on client, adding to upload list") uploadList = append(uploadList, entry) } // remove entry from remoteEntryModMap (process of elimination) delete(remoteEntryModMap, entry) } else { - fmt.Println(ansiUpload+entry+core.AnsiReset, "does not exist on server, adding to upload list") + fmt.Println(ansiUpload+entry+back.AnsiReset, "does not exist on server, adding to upload list") uploadList = append(uploadList, entry) } } // iterate over remaining entries in remoteEntryModMap for entry := range remoteEntryModMap { - fmt.Println(ansiDownload+entry+core.AnsiReset, "does not exist on client, adding to download list") + fmt.Println(ansiDownload+entry+back.AnsiReset, "does not exist on client, adding to download list") downloadList = append(downloadList, entry) } @@ -369,7 +370,7 @@ func syncLists(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows, timeSyn sftpSync(sshClient, sshEntryRoot, sshIsWindows, downloadList, uploadList) } else if !timeSynced { // do not call sftpSync if the client and server times are out of sync - core.Exit(1) + back.Exit(1) } fmt.Println("Client is synchronized with server") @@ -385,10 +386,10 @@ func deletionSync(deletions []string) { var filesDeleted bool for _, deletion := range deletions { filesDeleted = true // set a flag to indicate that files have been deleted (used to determine whether to print a gap between deletion and other messages) - fmt.Println(ansiDelete+deletion+core.AnsiReset, "has been sheared, removing locally (if it exists)") + fmt.Println(ansiDelete+deletion+back.AnsiReset, "has been sheared, removing locally (if it exists)") err := os.RemoveAll(core.TargetLocationFormat(deletion)) if err != nil { - core.PrintError("Sync failed - Failed to shear "+deletion+" locally: "+err.Error(), core.ErrorWrite, true) + back.PrintError("Sync failed - Failed to shear "+deletion+" locally: "+err.Error(), back.ErrorWrite, true) } } @@ -404,15 +405,15 @@ func folderSync(folders []string) { folderFullPath := core.TargetLocationFormat(folder) // check if folder already exists - isFile, isAccessible := core.TargetIsFile(folderFullPath, false, 1) + isFile, isAccessible := back.TargetIsFile(folderFullPath, false, 1) if !isFile && !isAccessible { err := os.MkdirAll(folderFullPath, 0700) if err != nil { - core.PrintError("Sync failed - Failed to create folder ("+folder+"): "+err.Error(), core.ErrorWrite, true) + back.PrintError("Sync failed - Failed to create folder ("+folder+"): "+err.Error(), back.ErrorWrite, true) } } else if isFile { - core.PrintError("Sync failed - Failed to create folder ("+folder+") - A file with the same name already exists", core.ErrorTargetExists, true) + back.PrintError("Sync failed - Failed to create folder ("+folder+") - A file with the same name already exists", core.ErrorTargetExists, true) } } } @@ -429,7 +430,7 @@ func RunJob(manualSync, returnLists bool) [3][]string { defer func(sshClient *ssh.Client) { err := sshClient.Close() if err != nil { - core.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) } }(sshClient) @@ -450,7 +451,7 @@ func RunJob(manualSync, returnLists bool) [3][]string { timeDiff := serverTime - clientTime if timeDiff < -45 || timeDiff > 45 { timeSynced = false - fmt.Print(core.AnsiError + "Client and server clocks are out of sync.\n\nPlease ensure both clocks are correct before attempting to sync again.\n\nA dry sync output will be printed below (if any operations would have been performed). It is strongly recommended to review it and manually update the modification times as applicable to ensure the correct version of each entry is kept.\n\nIf the client's clock is at fault, update the modification times of any entries pending upload, even if the correct (upload) operation is being performed on them. Failure to do so could result in entries being uploaded to the server with the incorrect modification times (could result in data loss).\n\n" + core.AnsiReset) + fmt.Print(back.AnsiError + "Client and server clocks are out of sync.\n\nPlease ensure both clocks are correct before attempting to sync again.\n\nA dry sync output will be printed below (if any operations would have been performed). It is strongly recommended to review it and manually update the modification times as applicable to ensure the correct version of each entry is kept.\n\nIf the client's clock is at fault, update the modification times of any entries pending upload, even if the correct (upload) operation is being performed on them. Failure to do so could result in entries being uploaded to the server with the incorrect modification times (could result in data loss).\n\n" + back.AnsiReset) } // sync new and updated entries @@ -461,6 +462,6 @@ func RunJob(manualSync, returnLists bool) [3][]string { return lists } syncLists(sshClient, sshEntryRoot, sshIsWindows, timeSynced, false, localEntryModMap, remoteEntryModMap) - core.Exit(0) // exit program if running non-interactively + back.Exit(0) // exit program if running non-interactively return lists // dummy return for when not returning lists } diff --git a/sync/common.go b/sync/common.go index f8fcf0b..28e8d7e 100644 --- a/sync/common.go +++ b/sync/common.go @@ -5,6 +5,7 @@ import ( "os" "strings" + "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/core" ) @@ -41,7 +42,7 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool) if err != nil { // do not print error as there is currently no way of seeing server-side errors // failure to add the target to the deletions list will exit the program and result in a client re-uploading the target (non-critical) - os.Exit(core.ErrorWrite) + os.Exit(back.ErrorWrite) } _ = fileToClose.Close() // error ignored; if the file could be created, it can probably be closed } @@ -52,11 +53,11 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool) targetLocationComplete := core.TargetLocationFormat(targetLocationIncomplete) var isFile bool if !onServer { // error if target does not exist on client, needed because os.RemoveAll does not return an error if target does not exist - isFile, _ = core.TargetIsFile(targetLocationComplete, true, 0) + isFile, _ = back.TargetIsFile(targetLocationComplete, true, 0) } err := os.RemoveAll(targetLocationComplete) if err != nil { - core.PrintError("Failed to remove local target: "+err.Error(), core.ErrorWrite, true) + back.PrintError("Failed to remove local target: "+err.Error(), back.ErrorWrite, true) } if !onServer && len(*deviceIDList) > 0 { // return the device ID if running on the client and a device ID exists (online mode) @@ -75,19 +76,19 @@ func RenameLocal(oldLocationIncomplete, newLocationIncomplete string, verifyOldL newLocation := core.TargetLocationFormat(newLocationIncomplete) if verifyOldLocationExists { - core.TargetIsFile(oldLocation, true, 0) + back.TargetIsFile(oldLocation, true, 0) } // ensure newLocation does not exist - _, isAccessible := core.TargetIsFile(newLocation, false, 0) + _, isAccessible := back.TargetIsFile(newLocation, false, 0) if isAccessible { - core.PrintError("\""+newLocation+"\" already exists", core.ErrorTargetExists, true) + back.PrintError("\""+newLocation+"\" already exists", core.ErrorTargetExists, true) } // rename oldLocation to newLocation err := os.Rename(oldLocation, newLocation) if err != nil { - core.PrintError("Failed to rename - Does the target containing directory exist?", core.ErrorTargetNotFound, true) + back.PrintError("Failed to rename - Does the target containing directory exist?", back.ErrorTargetNotFound, true) } // do not exit program, as this function is used as part of RenameRemoteFromClient @@ -103,7 +104,7 @@ func AddFolderLocal(targetLocationIncomplete string) { if os.IsExist(err) { fmt.Println(ansiUpload + "Directory already exists - libmutton will still ensure it exists on the server") } else { - core.PrintError("Failed to create directory: "+err.Error(), core.ErrorWrite, true) + back.PrintError("Failed to create directory: "+err.Error(), back.ErrorWrite, true) } } diff --git a/sync/commonUNIX.go b/sync/commonUNIX.go index cd8f019..077c647 100644 --- a/sync/commonUNIX.go +++ b/sync/commonUNIX.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" + "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/core" ) @@ -24,9 +25,9 @@ func WalkEntryDir() ([]string, []string) { // check for errors encountered while walking directory if err != nil { if os.IsNotExist(err) { - core.PrintError("The entry directory does not exist - Initialize libmutton to create it", core.ErrorOther, true) + back.PrintError("The entry directory does not exist - Initialize libmutton to create it", back.ErrorOther, true) } else { - core.PrintError("An unexpected error occurred while generating the entry list: "+err.Error(), core.ErrorOther, true) + back.PrintError("An unexpected error occurred while generating the entry list: "+err.Error(), back.ErrorOther, true) } } diff --git a/sync/init.go b/sync/init.go index ed3dda2..ac13db0 100644 --- a/sync/init.go +++ b/sync/init.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/core" ) @@ -23,14 +24,14 @@ func DeviceIDGen(oldDeviceID string) (string, string) { // create new device ID file (locally) fileToClose, err := os.OpenFile(core.ConfigDir+core.PathSeparator+"devices"+core.PathSeparator+newDeviceID, os.O_CREATE|os.O_WRONLY, 0600) if err != nil { - core.PrintError("Failed to create local device ID file: "+err.Error(), core.ErrorWrite, true) + back.PrintError("Failed to create local device ID file: "+err.Error(), back.ErrorWrite, true) } _ = fileToClose.Close() // error ignored; if the file could be created, it can probably be closed // remove old device ID file (locally; may not exist) err = os.RemoveAll(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + oldDeviceID) if err != nil { - core.PrintError("Failed to remove old device ID file (locally): "+err.Error(), core.ErrorWrite, true) + back.PrintError("Failed to remove old device ID file (locally): "+err.Error(), back.ErrorWrite, true) } // register new device ID with server and fetch remote EntryRoot and OS type @@ -40,7 +41,7 @@ func DeviceIDGen(oldDeviceID string) (string, string) { sshEntryRootSSHIsWindows := strings.Split(GetSSHOutput(sshClient, "libmuttonserver register", newDeviceID+"\n"+oldDeviceID), core.FSSpace) err = sshClient.Close() if err != nil { - core.PrintError("Init failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Init failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) } return sshEntryRootSSHIsWindows[0], sshEntryRootSSHIsWindows[1] diff --git a/sync/oneOff.go b/sync/oneOff.go index 85886de..2fe1538 100644 --- a/sync/oneOff.go +++ b/sync/oneOff.go @@ -3,6 +3,7 @@ package sync import ( "strings" + "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/core" ) @@ -26,11 +27,11 @@ func ShearRemoteFromClient(targetLocationIncomplete string, forceOffline bool) { // close the SSH client err := sshClient.Close() if err != nil { - core.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) } } - core.Exit(0) // sync is not required after shearing since the target has already been removed from the local system + back.Exit(0) // sync is not required after shearing since the target has already been removed from the local system } // RenameRemoteFromClient renames oldLocationIncomplete to newLocationIncomplete on the local system and calls the server to perform the rename remotely and add the old target to the deletions list. @@ -52,11 +53,11 @@ func RenameRemoteFromClient(oldLocationIncomplete, newLocationIncomplete string, // close the SSH client err := sshClient.Close() if err != nil { - core.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) } } - core.Exit(0) + back.Exit(0) } // AddFolderRemoteFromClient creates a new entry-containing directory on the local system and calls the server to create the folder remotely. @@ -75,9 +76,9 @@ func AddFolderRemoteFromClient(targetLocationIncomplete string, forceOffline boo // close the SSH client err := sshClient.Close() if err != nil { - core.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) + back.PrintError("Sync failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true) } } - core.Exit(0) + back.Exit(0) } diff --git a/sync/server.go b/sync/server.go index 7191dae..b7fdfe0 100644 --- a/sync/server.go +++ b/sync/server.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/libmutton/core" ) @@ -17,7 +18,7 @@ func GetRemoteDataFromServer(clientDeviceID string) { modList := getModTimes(entryList) deletionsList, err := os.ReadDir(core.ConfigDir + core.PathSeparator + "deletions") if err != nil { - core.PrintError("Failed to read the deletions directory: "+err.Error(), core.ErrorRead, true) + back.PrintError("Failed to read the deletions directory: "+err.Error(), back.ErrorRead, true) } // print the current UNIX timestamp to stdout