From 88b7a6be6d915aeee1b3e4ba52248a428e41bc3c Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Tue, 13 Aug 2024 17:20:15 -0400 Subject: [PATCH] Create all config/entry files (both local and remote) with 0600 permissions --- README.md | 2 +- core/configParser.go | 6 +++--- core/copyDARWIN.go | 6 +++--- core/copyTERMUX.go | 6 +++--- core/copyUNIXGeneric.go | 6 +++--- core/copyWIN.go | 6 +++--- core/init.go | 8 ++++---- core/utilitiesMisc.go | 6 +++--- libmuttonserver.go | 3 ++- sync/client.go | 27 +++++++++++++++++---------- sync/common.go | 7 ++++--- sync/commonUNIX.go | 2 +- sync/commonWIN.go | 2 +- sync/init.go | 14 +++++++++++--- sync/server.go | 2 +- wiki/bugs.md | 4 ++-- 16 files changed, 62 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index be13181..91fab9b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ See the [developer guide](https://github.com/rwinkhart/libmutton/blob/main/wiki/ #### Release v0.2.1 - [x] Only run getSSHClient once to prevent being asked for keyfile password multiple times - [x] After this, handle all errors in sync/client.go -- [ ] Ensure all config files and entry files are created with 0600 permissions +- [x] Ensure all config files and entry files are created with 0600 permissions - [x] Add error-specific exit codes - [x] Split into separate repos 1. libmutton: backend package (rename to core), sync package, libmuttonserver diff --git a/core/configParser.go b/core/configParser.go index 454fc3e..2ab583f 100644 --- a/core/configParser.go +++ b/core/configParser.go @@ -13,7 +13,7 @@ import ( func loadConfig() *ini.File { cfg, err := ini.Load(ConfigPath) if err != nil { - fmt.Println(AnsiError + "Failed to load libmutton.ini: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to load libmutton.ini:", err.Error()+AnsiReset) os.Exit(101) } return cfg @@ -56,7 +56,7 @@ func GenDeviceIDList(errorOnFail bool) *[]fs.DirEntry { // create a slice of all registered devices deviceIDList, err := os.ReadDir(ConfigDir + PathSeparator + "devices") if err != nil && errorOnFail { - fmt.Println(AnsiError + "Failed to read the devices directory: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to read the devices directory:", err.Error()+AnsiReset) os.Exit(101) } return &deviceIDList @@ -93,7 +93,7 @@ func WriteConfig(valuesToWrite [][3]string, append bool) { // save to libmutton.ini err := cfg.SaveTo(ConfigPath) if err != nil { - fmt.Println(AnsiError + "Failed to save libmutton.ini: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to save libmutton.ini:", err.Error()+AnsiReset) os.Exit(102) } } diff --git a/core/copyDARWIN.go b/core/copyDARWIN.go index 10962d4..e3fac23 100644 --- a/core/copyDARWIN.go +++ b/core/copyDARWIN.go @@ -16,7 +16,7 @@ func copyField(executableName, copySubject string) { writeToStdin(cmd, copySubject) err := cmd.Run() if err != nil { - fmt.Println(AnsiError + "Failed to copy to clipboard: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to copy to clipboard:", err.Error()+AnsiReset) os.Exit(110) } @@ -40,7 +40,7 @@ func clipClear(oldContents string) { cmd := exec.Command("pbpaste") newContents, err := cmd.Output() if err != nil { - fmt.Println(AnsiError + "Failed to read clipboard contents: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to read clipboard contents:", err.Error()+AnsiReset) os.Exit(110) } @@ -49,7 +49,7 @@ func clipClear(oldContents string) { writeToStdin(cmd, "") err = cmd.Run() if err != nil { - fmt.Println(AnsiError + "Failed to clear clipboard: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to clear clipboard:", err.Error()+AnsiReset) os.Exit(110) } } diff --git a/core/copyTERMUX.go b/core/copyTERMUX.go index d7e5f86..904ffb1 100644 --- a/core/copyTERMUX.go +++ b/core/copyTERMUX.go @@ -16,7 +16,7 @@ func copyField(executableName, copySubject string) { writeToStdin(cmd, copySubject) err := cmd.Run() if err != nil { - fmt.Println(AnsiError + "Failed to copy to clipboard: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to copy to clipboard:", err.Error()+AnsiReset) os.Exit(110) } @@ -40,7 +40,7 @@ func clipClear(oldContents string) { cmd := exec.Command("termux-clipboard-get") newContents, err := cmd.Output() if err != nil { - fmt.Println(AnsiError + "Failed to read clipboard contents: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to read clipboard contents:", err.Error()+AnsiReset) os.Exit(110) } @@ -49,7 +49,7 @@ func clipClear(oldContents string) { writeToStdin(cmd, "") err = cmd.Run() if err != nil { - fmt.Println(AnsiError + "Failed to clear clipboard: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to clear clipboard:", err.Error()+AnsiReset) os.Exit(110) } } diff --git a/core/copyUNIXGeneric.go b/core/copyUNIXGeneric.go index 4dade11..6e51f38 100644 --- a/core/copyUNIXGeneric.go +++ b/core/copyUNIXGeneric.go @@ -27,7 +27,7 @@ func copyField(executableName, copySubject string) { writeToStdin(cmd, copySubject) err := cmd.Run() if err != nil { - fmt.Println(AnsiError + "Failed to copy to clipboard: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to copy to clipboard:", err.Error()+AnsiReset) os.Exit(110) } @@ -65,7 +65,7 @@ func clipClear(oldContents string) { // read current clipboard contents newContents, err := cmdPaste.Output() if err != nil { - fmt.Println(AnsiError + "Failed to read clipboard contents: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to read clipboard contents:", err.Error()+AnsiReset) os.Exit(110) } @@ -73,7 +73,7 @@ func clipClear(oldContents string) { if oldContents == strings.TrimRight(string(newContents), "\r\n") { err = cmdClear.Run() if err != nil { - fmt.Println(AnsiError + "Failed to clear clipboard: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to clear clipboard:", err.Error()+AnsiReset) os.Exit(110) } } diff --git a/core/copyWIN.go b/core/copyWIN.go index fc78ae8..096f3cf 100644 --- a/core/copyWIN.go +++ b/core/copyWIN.go @@ -15,7 +15,7 @@ func copyField(executableName, copySubject string) { cmd := exec.Command("powershell.exe", "-c", fmt.Sprintf("echo '%s' | Set-Clipboard", strings.ReplaceAll(copySubject, "'", "''"))) err := cmd.Run() if err != nil { - fmt.Println(AnsiError + "Failed to copy to clipboard: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to copy to clipboard:", err.Error()+AnsiReset) os.Exit(110) } @@ -39,7 +39,7 @@ func clipClear(oldContents string) { cmd := exec.Command("powershell.exe", "-c", "Get-Clipboard") newContents, err := cmd.Output() if err != nil { - fmt.Println(AnsiError + "Failed to read clipboard contents: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to read clipboard contents:", err.Error()+AnsiReset) os.Exit(110) } @@ -47,7 +47,7 @@ func clipClear(oldContents string) { cmd = exec.Command("powershell.exe", "-c", "Set-Clipboard") err = cmd.Run() if err != nil { - fmt.Println(AnsiError + "Failed to clear clipboard: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to clear clipboard:", err.Error()+AnsiReset) os.Exit(110) } } diff --git a/core/init.go b/core/init.go index 32bc3a8..cca4f5b 100644 --- a/core/init.go +++ b/core/init.go @@ -45,7 +45,7 @@ func GpgKeyGen() string { cmd.Stdin = os.Stdin err := cmd.Run() if err != nil { - fmt.Println(AnsiError + "Failed to generate GPG key: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to generate GPG key:", err.Error()+AnsiReset) os.Exit(111) } @@ -58,7 +58,7 @@ func DirInit(preserveOldConfigDir bool) string { // create EntryRoot err := os.MkdirAll(EntryRoot, 0700) if err != nil { - fmt.Println(AnsiError + "Failed to create \"" + EntryRoot + "\": " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to create \""+EntryRoot+"\":", err.Error()+AnsiReset) os.Exit(102) } @@ -71,7 +71,7 @@ func DirInit(preserveOldConfigDir bool) string { if isAccessible { err = os.RemoveAll(ConfigDir) if err != nil { - fmt.Println(AnsiError + "Failed to remove existing config directory: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to remove existing config directory:", err.Error()+AnsiReset) os.Exit(102) } } @@ -80,7 +80,7 @@ func DirInit(preserveOldConfigDir bool) string { // create config directory w/devices subdirectory err = os.MkdirAll(ConfigDir+PathSeparator+"devices", 0700) if err != nil { - fmt.Println(AnsiError + "Failed to create \"" + ConfigDir + "\": " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to create \""+ConfigDir+"\":", err.Error()+AnsiReset) os.Exit(102) } diff --git a/core/utilitiesMisc.go b/core/utilitiesMisc.go index 573e7c1..c391f51 100644 --- a/core/utilitiesMisc.go +++ b/core/utilitiesMisc.go @@ -52,7 +52,7 @@ func WriteEntry(targetLocation string, entryData []string, verifyEntryDoesNotExi encryptedBytes := EncryptGPG(entryData) err := os.WriteFile(targetLocation, encryptedBytes, 0600) if err != nil { - fmt.Println(AnsiError + "Failed to write to file: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to write to file:", err.Error()+AnsiReset) os.Exit(102) } } @@ -61,7 +61,7 @@ func WriteEntry(targetLocation string, entryData []string, verifyEntryDoesNotExi func writeToStdin(cmd *exec.Cmd, input string) { stdin, err := cmd.StdinPipe() if err != nil { - fmt.Println(AnsiError + "Failed to access stdin for system command: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to access stdin for system command:", err.Error()+AnsiReset) os.Exit(111) } @@ -77,7 +77,7 @@ func writeToStdin(cmd *exec.Cmd, input string) { func CreateTempFile() *os.File { tempFile, err := os.CreateTemp("", "*.markdown") if err != nil { - fmt.Println(AnsiError + "Failed to create temporary file: " + err.Error() + AnsiReset) + fmt.Println(AnsiError+"Failed to create temporary file:", err.Error()+AnsiReset) os.Exit(102) } return tempFile diff --git a/libmuttonserver.go b/libmuttonserver.go index 1a275ab..0e7cc59 100644 --- a/libmuttonserver.go +++ b/libmuttonserver.go @@ -57,7 +57,8 @@ func main() { // register a new device ID // stdin[0] is expected to be the device ID // stdin[1] is expected to be the old device ID (for removal) - _, _ = os.Create(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + stdin[0]) // errors ignored; failure unlikely to occur if init was successful; "register" is not a user-facing argument and thus the error would not be visible + fileToClose, _ := os.OpenFile(core.ConfigDir+core.PathSeparator+"devices"+core.PathSeparator+stdin[0], os.O_CREATE|os.O_WRONLY, 0600) // errors ignored; failure unlikely to occur if init was successful; "register" is not a user-facing argument and thus the error would not be visible + _ = fileToClose.Close() _ = os.RemoveAll(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + stdin[1]) // print EntryRoot and bool indicating OS type to stdout for client to store in config fmt.Print(core.EntryRoot + sync.FSSpace + strconv.FormatBool(core.IsWindows)) diff --git a/sync/client.go b/sync/client.go index 7858959..8d0411f 100644 --- a/sync/client.go +++ b/sync/client.go @@ -53,7 +53,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { case 6: isWindows, err = strconv.ParseBool(key) if err != nil { - fmt.Println(core.AnsiError + "Sync failed - Unable to parse server OS type: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"Sync failed - Unable to parse server OS type:", err.Error()+core.AnsiReset) os.Exit(101) } } @@ -82,7 +82,7 @@ func GetSSHClient(manualSync bool) (*ssh.Client, string, bool) { var hostKeyCallback ssh.HostKeyCallback hostKeyCallback, err = knownhosts.New(core.Home + core.PathSeparator + ".ssh" + core.PathSeparator + "known_hosts") if err != nil { - fmt.Println(core.AnsiError + "Sync failed - Unable to read known hosts file: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"Sync failed - Unable to read known hosts file:", err.Error()+core.AnsiReset) os.Exit(101) } @@ -157,7 +157,7 @@ func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string } serverTime, err := strconv.ParseInt(outputSlice[0], 10, 64) if err != nil { - fmt.Println(core.AnsiError+"Sync failed - Unable to parse server time: ", err.Error()+core.AnsiReset) + fmt.Println(core.AnsiError+"Sync failed - Unable to parse server time:", err.Error()+core.AnsiReset) os.Exit(101) } entries := strings.Split(outputSlice[1], FSMisc)[1:] @@ -171,7 +171,7 @@ func getRemoteDataFromClient(sshClient *ssh.Client, manualSync bool) (map[string for _, modString := range modsStrings { mod, err = strconv.ParseInt(modString, 10, 64) if err != nil { - fmt.Println(core.AnsiError+"Sync failed - Unable to parse mod time: ", err.Error()+core.AnsiReset) + fmt.Println(core.AnsiError+"Sync failed - Unable to parse mod time:", err.Error()+core.AnsiReset) os.Exit(101) } mods = append(mods, mod) @@ -224,7 +224,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow defer func(sftpClient *sftp.Client) { err = sftpClient.Close() if err != nil { - fmt.Println(core.AnsiError+"Sync failed - Unable to close SFTP client: ", err.Error()+core.AnsiReset) + fmt.Println(core.AnsiError+"Sync failed - Unable to close SFTP client:", err.Error()+core.AnsiReset) os.Exit(104) } }(sftpClient) @@ -261,7 +261,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow // create local file var localFile *os.File - localFile, err = os.Create(localEntryFullPath) + localFile, err = os.OpenFile(localEntryFullPath, os.O_CREATE|os.O_WRONLY, 0600) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to create local file:", err.Error()+core.AnsiReset) os.Exit(102) @@ -318,7 +318,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow // create remote file var remoteFile *sftp.File - remoteFile, err = sftpClient.Create(remoteEntryFullPath) + remoteFile, err = sftpClient.OpenFile(remoteEntryFullPath, os.O_CREATE|os.O_WRONLY) if err != nil { fmt.Println(core.AnsiError+"Sync failed - Unable to create remote file ("+remoteEntryFullPath+"):", err.Error()+core.AnsiReset) os.Exit(102) @@ -335,6 +335,13 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot string, sshIsWindows bool, dow _ = localFile.Close() // errors ignored; if the files could be opened/created, it can probably be closed _ = remoteFile.Close() + // set permissions on remote file + err = sftpClient.Chmod(remoteEntryFullPath, 0600) + if err != nil { + fmt.Println(core.AnsiError+"Sync failed - Unable to set permissions on remote file:", err.Error()+core.AnsiReset) + os.Exit(103) + } + // set the modification time of the remote file to match the value saved from the local file (from before the upload) err = sftpClient.Chtimes(remoteEntryFullPath, time.Now(), modTime) } @@ -396,7 +403,7 @@ func deletionSync(deletions []string) { fmt.Println(ansiDelete+deletion+core.AnsiReset, "has been sheared, removing locally (if it exists)") err := os.RemoveAll(core.TargetLocationFormat(deletion)) if err != nil { - fmt.Println(core.AnsiError + "Sync failed - Failed to shear " + deletion + " locally: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"Sync failed - Failed to shear "+deletion+" locally:", err.Error()+core.AnsiReset) os.Exit(102) } } @@ -457,7 +464,7 @@ func folderSync(folders []string) { if !isFile && !isAccessible { err := os.MkdirAll(folderFullPath, 0700) if err != nil { - fmt.Println(core.AnsiError + "Sync failed - Failed to create folder \"" + folder + "\": " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"Sync failed - Failed to create folder \""+folder+"\":", err.Error()+core.AnsiReset) os.Exit(102) } } else if isFile { @@ -475,7 +482,7 @@ func RunJob(manualSync bool) { defer func(sshClient *ssh.Client) { err := sshClient.Close() if err != nil { - fmt.Println(core.AnsiError+"Sync failed - Unable to close SSH client: ", err.Error()+core.AnsiReset) + fmt.Println(core.AnsiError+"Sync failed - Unable to close SSH client:", err.Error()+core.AnsiReset) os.Exit(104) } }(sshClient) diff --git a/sync/common.go b/sync/common.go index 5a5176f..db67091 100644 --- a/sync/common.go +++ b/sync/common.go @@ -36,12 +36,13 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string { if onServer { for _, device := range *deviceIDList { if device.Name() != clientDeviceID { - _, err := os.Create(core.ConfigDir + core.PathSeparator + "deletions" + core.PathSeparator + device.Name() + FSSpace + strings.ReplaceAll(targetLocationIncomplete, "/", FSPath)) + fileToClose, err := os.OpenFile(core.ConfigDir+core.PathSeparator+"deletions"+core.PathSeparator+device.Name()+FSSpace+strings.ReplaceAll(targetLocationIncomplete, "/", FSPath), os.O_CREATE|os.O_WRONLY, 0600) 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(102) } + _ = fileToClose.Close() // error ignored; if the file could be created, it can probably be closed } } } @@ -53,7 +54,7 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) string { } err := os.RemoveAll(targetLocationComplete) if err != nil { - fmt.Println(core.AnsiError + "Failed to remove local target: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"Failed to remove local target:", err.Error()+core.AnsiReset) os.Exit(102) } @@ -103,7 +104,7 @@ func AddFolderLocal(targetLocationIncomplete string) { fmt.Println(core.AnsiError + "Directory already exists" + core.AnsiReset) os.Exit(106) } else { - fmt.Println(core.AnsiError + "Failed to create directory: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"Failed to create directory:", err.Error()+core.AnsiReset) os.Exit(102) } } diff --git a/sync/commonUNIX.go b/sync/commonUNIX.go index 0d55ce8..9727b01 100644 --- a/sync/commonUNIX.go +++ b/sync/commonUNIX.go @@ -28,7 +28,7 @@ func WalkEntryDir() ([]string, []string) { fmt.Println(core.AnsiError+"The entry directory does not exist - Run \""+os.Args[0], "init"+"\" to create it"+core.AnsiReset) } else { // otherwise, print the source of the error - fmt.Println(core.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"An unexpected error occurred while generating the entry list:", err.Error()+core.AnsiReset) } os.Exit(111) } diff --git a/sync/commonWIN.go b/sync/commonWIN.go index b1a1eaf..c26da70 100644 --- a/sync/commonWIN.go +++ b/sync/commonWIN.go @@ -29,7 +29,7 @@ func WalkEntryDir() ([]string, []string) { fmt.Println(joinErrorWithEXE("The entry directory does not exist - Run \"", " init"+"\" to create it")) } else { // otherwise, print the source of the error - fmt.Println(core.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"An unexpected error occurred while generating the entry list:", err.Error()+core.AnsiReset) } os.Exit(111) } diff --git a/sync/init.go b/sync/init.go index 373d1cd..f3e77d8 100644 --- a/sync/init.go +++ b/sync/init.go @@ -23,11 +23,12 @@ func DeviceIDGen(oldDeviceID string) (string, string) { newDeviceID := deviceIDPrefix + "-" + deviceIDSuffix // create new device ID file (locally) - _, err := os.Create(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + newDeviceID) // TODO remove existing device ID file if it exists (from both client and server) + fileToClose, err := os.OpenFile(core.ConfigDir+core.PathSeparator+"devices"+core.PathSeparator+newDeviceID, os.O_CREATE|os.O_WRONLY, 0600) if err != nil { - fmt.Println(core.AnsiError + "Failed to create local device ID file: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"Failed to create local device ID file:", err.Error()+core.AnsiReset) os.Exit(102) } + _ = fileToClose.Close() // error ignored; if the file could be created, it can probably be closed // register new device ID with server and fetch remote EntryRoot and OS type // also removes the old device ID file (remotely) @@ -36,11 +37,18 @@ func DeviceIDGen(oldDeviceID string) (string, string) { defer func(sshClient *ssh.Client) { err = sshClient.Close() if err != nil { - fmt.Println(core.AnsiError + "Init failed - Unable to close SSH client: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"Init failed - Unable to close SSH client:", err.Error()+core.AnsiReset) os.Exit(104) } }(sshClient) sshEntryRootSSHIsWindows := strings.Split(GetSSHOutput(sshClient, "libmuttonserver register", newDeviceID+"\n"+oldDeviceID), FSSpace) + // remove old device ID file (locally; may not exist) + err = os.RemoveAll(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + oldDeviceID) + if err != nil { + fmt.Println(core.AnsiError+"Failed to remove old device ID file (locally):", err.Error()+core.AnsiReset) + os.Exit(102) + } + return sshEntryRootSSHIsWindows[0], sshEntryRootSSHIsWindows[1] } diff --git a/sync/server.go b/sync/server.go index a3e2264..00f8ff9 100644 --- a/sync/server.go +++ b/sync/server.go @@ -17,7 +17,7 @@ func GetRemoteDataFromServer(clientDeviceID string) { modList := getModTimes(entryList) deletionsList, err := os.ReadDir(core.ConfigDir + core.PathSeparator + "deletions") if err != nil { - fmt.Println(core.AnsiError + "Failed to read the deletions directory: " + err.Error() + core.AnsiReset) + fmt.Println(core.AnsiError+"Failed to read the deletions directory:", err.Error()+core.AnsiReset) os.Exit(101) } diff --git a/wiki/bugs.md b/wiki/bugs.md index fb00e30..0037d51 100644 --- a/wiki/bugs.md +++ b/wiki/bugs.md @@ -1,8 +1,8 @@ ## Known Bugs - libmutton - On Windows, GPG is sometimes (seems unpredictable) incredibly slow to start (often after a reboot), leading to many operations seemingly hanging - **This will be addressed** in the migration off of GPG that will take place before v1.0.0 -- If a client is reconfigured to use a new device ID, the old one will remain present on the server. This will not cause any immediate issues, however it will lead to the server creating unnecessary deletions files for the old device ID. Currently, it is recommended to manually delete the old device ID from the server's "devices" directory. - - **This will be addressed** in v0.2.1 +- ~~If a client is reconfigured to use a new device ID, the old one will remain present on the server. This will not cause any immediate issues, however it will lead to the server creating unnecessary deletions files for the old device ID. Currently, it is recommended to manually delete the old device ID from the server's "devices" directory.~~ + - **Addressed** for v0.2.1 - ~~Using a password-protected SSH identity file will prompt for the key's password multiple times when syncing~~ - **Addressed** for v0.2.1 - Password-protected SSH identity files currently only prompt for password entry in the CLI, and thus they are not yet supported in GUI/TUI implementations