diff --git a/src/backend/configParser.go b/src/backend/configParser.go index 4dd5801..4713c20 100644 --- a/src/backend/configParser.go +++ b/src/backend/configParser.go @@ -6,16 +6,23 @@ import ( "os" ) -// ReadConfig reads the libmutton.ini file and returns a slice of values for the specified keys -// requires readKeys: a slice of key names (indicates requested values) -// requires missingValueError: an error message to display if a key is missing a value, set to "" for auto-generated or "0" to exit silently with status 0 -// returns config: a slice of values for the specified keys -func ReadConfig(readKeys []string, missingValueError string) []string { +// loadConfig loads the libmutton.ini file and returns the configuration +// utility function for ParseConfig and WriteConfig, do not call directly +func loadConfig() *ini.File { cfg, err := ini.Load(ConfigPath) if err != nil { fmt.Println(AnsiError + "Failed to load libmutton.ini: " + err.Error() + AnsiReset) os.Exit(1) } + return cfg +} + +// ParseConfig reads the libmutton.ini file and returns a slice of values for the specified keys +// requires readKeys: a slice of key names (indicates requested values) +// requires missingValueError: an error message to display if a key is missing a value, set to "" for auto-generated or "0" to exit silently with status 0 +// returns config: a slice of values for the specified keys +func ParseConfig(readKeys []string, missingValueError string) []string { + cfg := loadConfig() var config []string @@ -41,7 +48,44 @@ func ReadConfig(readKeys []string, missingValueError string) []string { return config } -// libmuttn.ini layout +// WriteConfig writes the provided key-value pairs to the libmutton.ini file +func WriteConfig(configFileMap map[string]string, append bool) { + var cfg *ini.File + var libmuttonSection *ini.Section + + if append { + // load existing ini file + cfg = loadConfig() + + // acquire LIBMUTTON section + libmuttonSection, _ = cfg.GetSection("LIBMUTTON") + } else { + // create empty ini file + cfg = ini.Empty() + + // create LIBMUTTON section + libmuttonSection, _ = cfg.NewSection("LIBMUTTON") + + // set default textEditor value + if configFileMap["textEditor"] == "" { + configFileMap["textEditor"] = textEditorFallback() + } + } + + // write provided configFileMap key-value pairs to the LIBMUTTON section + for key, value := range configFileMap { + libmuttonSection.Key(key).SetValue(value) + } + + // save the new config file + err := cfg.SaveTo(ConfigPath) + if err != nil { + fmt.Println(AnsiError + "Failed to save libmutton.ini: " + err.Error() + AnsiReset) + os.Exit(1) + } +} + +// libmutton.ini layout // [LIBMUTTON] // gpgID = // textEditor = @@ -51,3 +95,9 @@ func ReadConfig(readKeys []string, missingValueError string) []string { // sshKey = // sshKeyProtected = // netPinEnabled = +// sshEntryRoot = +// sshIsWindows = + +// Developers of alternative clients: +// If you are adding additional settings to the config file, +// please create a new section heading for your app-specific settings. diff --git a/src/backend/gpg.go b/src/backend/gpg.go index 24edb69..ef11d79 100644 --- a/src/backend/gpg.go +++ b/src/backend/gpg.go @@ -8,7 +8,6 @@ import ( ) // TODO GPG support is a temporary feature - it will be replaced with a different encryption scheme in the future -// These functions may continue to exist after that point, but consider them deprecated // DecryptGPG decrypts a GPG-encrypted file and returns the contents as a slice of (trimmed) strings func DecryptGPG(targetLocation string) []string { @@ -24,7 +23,7 @@ func DecryptGPG(targetLocation string) []string { // EncryptGPG encrypts a slice of strings using GPG and returns the encrypted data as a byte slice func EncryptGPG(input []string) []byte { - cmd := exec.Command("gpg", "-q", "-r", ReadConfig([]string{"gpgID"}, "")[0], "-e") + cmd := exec.Command("gpg", "-q", "-r", ParseConfig([]string{"gpgID"}, "")[0], "-e") writeToStdin(cmd, strings.Join(input, "\n")) encryptedBytes, err := cmd.Output() if err != nil { diff --git a/src/backend/init.go b/src/backend/init.go index 0e048ce..58cbbbf 100644 --- a/src/backend/init.go +++ b/src/backend/init.go @@ -9,25 +9,6 @@ import ( "time" ) -// TempInit ensures libmutton directories exist and writes the libmutton configuration file -// TODO if run in append mode, extend old config file with new values, rather than creating from scratch -func TempInit(configFileMap map[string]string, append bool) { - // create EntryRoot and ConfigDir - DirInit(append) - - if configFileMap["textEditor"] == "" { - configFileMap["textEditor"] = textEditorFallback() - } - - // create and write config file - configFile, _ := os.OpenFile(ConfigPath, os.O_CREATE|os.O_WRONLY, 0600) - defer configFile.Close() - configFile.WriteString("[LIBMUTTON]\n") - for key, value := range configFileMap { - configFile.WriteString(key + " = " + value + "\n") - } -} - // GpgUIDListGen generates a list of all GPG key IDs on the system and returns them as a slice of strings func GpgUIDListGen() []string { cmd := exec.Command("gpg", "-k", "--with-colons") diff --git a/src/cli/edit.go b/src/cli/edit.go index ea600a6..26ca406 100644 --- a/src/cli/edit.go +++ b/src/cli/edit.go @@ -72,7 +72,7 @@ func GenUpdate(targetLocation string, hideSecrets bool) { func editNote(baseNote []string) ([]string, bool) { tempFile := backend.CreateTempFile() defer os.Remove(tempFile.Name()) - editor := backend.ReadConfig([]string{"textEditor"}, "")[0] + editor := backend.ParseConfig([]string{"textEditor"}, "")[0] // write baseNote to tempFile (if it is not empty) if len(baseNote) > 0 { diff --git a/src/cli/init.go b/src/cli/init.go index a74ff35..d5d7b99 100644 --- a/src/cli/init.go +++ b/src/cli/init.go @@ -8,7 +8,7 @@ import ( "strconv" ) -// TempInitCli initializes the MUTN environment based on user input +// TempInitCli initializes the MUTN environment based on user input (will be replaced with a TUI menu) func TempInitCli() { // gpgID var gpgID string @@ -39,17 +39,22 @@ func TempInitCli() { sshKey := input("SSH private identity file path:") // TODO implement generator and selector sshKeyProtected := inputBinary("Is the identity file password-protected?") - // write config file - backend.TempInit(map[string]string{"textEditor": textEditor, "gpgID": gpgID, "sshUser": sshUser, "sshIP": sshIP, "sshPort": sshPort, "sshKey": sshKey, "sshKeyProtected": strconv.FormatBool(sshKeyProtected), "sshEntryRoot": "null", "sshIsWindows": "null"}, false) + // initialize libmutton directories + backend.DirInit(false) - // generate device ID + // write config file (temporarily assigns sshEntryRoot and sshIsWindows to null to pass initial device ID registration) + backend.WriteConfig(map[string]string{"textEditor": textEditor, "gpgID": gpgID, "sshUser": sshUser, "sshIP": sshIP, "sshPort": sshPort, "sshKey": sshKey, "sshKeyProtected": strconv.FormatBool(sshKeyProtected), "sshEntryRoot": "null", "sshIsWindows": "null"}, false) + + // generate and register device ID sshEntryRoot, sshIsWindows := sync.DeviceIDGen() - // update config file with sshEntryRoot and sshIsWindows TODO append to existing config file - backend.TempInit(map[string]string{"textEditor": textEditor, "gpgID": gpgID, "sshUser": sshUser, "sshIP": sshIP, "sshPort": sshPort, "sshKey": sshKey, "sshKeyProtected": strconv.FormatBool(sshKeyProtected), "sshEntryRoot": sshEntryRoot, "sshIsWindows": sshIsWindows}, true) + // update config file with sshEntryRoot and sshIsWindows + backend.WriteConfig(map[string]string{"sshEntryRoot": sshEntryRoot, "sshIsWindows": sshIsWindows}, true) } else { - // write config file - backend.TempInit(map[string]string{"textEditor": textEditor, "gpgID": gpgID}, false) - } + // initialize libmutton directories + backend.DirInit(false) + // write config file + backend.WriteConfig(map[string]string{"textEditor": textEditor, "gpgID": gpgID}, false) + } } diff --git a/src/sync/client.go b/src/sync/client.go index ad9bf5e..58ec610 100644 --- a/src/sync/client.go +++ b/src/sync/client.go @@ -30,7 +30,7 @@ func getSSHClient(manualSync bool) (*ssh.Client, string, bool) { } else { missingValueError = "0" } - sshUserConfig = backend.ReadConfig([]string{"sshUser", "sshIP", "sshPort", "sshKey", "sshKeyProtected", "sshEntryRoot", "sshIsWindows"}, missingValueError) + sshUserConfig = backend.ParseConfig([]string{"sshUser", "sshIP", "sshPort", "sshKey", "sshKeyProtected", "sshEntryRoot", "sshIsWindows"}, missingValueError) var user, ip, port, keyFile, keyFileProtected, entryRoot string var isWindows bool @@ -65,7 +65,7 @@ func getSSHClient(manualSync bool) (*ssh.Client, string, bool) { if keyFileProtected != "true" { parsedKey, err = ssh.ParsePrivateKey(key) } else { - parsedKey, err = ssh.ParsePrivateKeyWithPassphrase(key, inputKeyFilePassphrase()) // TODO test passphrase-protected keys + parsedKey, err = ssh.ParsePrivateKeyWithPassphrase(key, inputKeyFilePassphrase()) } if err != nil { fmt.Println(backend.AnsiError+"Sync failed - Unable to parse private key:", keyFile+backend.AnsiReset) @@ -99,7 +99,7 @@ func getSSHClient(manualSync bool) (*ssh.Client, string, bool) { } // GetSSHOutput runs a command over SSH and returns the output as a string -// TODO run getSSHClient() only ONCE (from RunJob) - this only saves re-creating the client, not re-establishing the connection, so it may not be worth it +// TODO run getSSHClient() only ONCE (from RunJob) - this saves re-creating the client AND prevents prompting for keyfile passphrase multiple times func GetSSHOutput(cmd, stdin string, manualSync bool) string { sshClient, _, _ := getSSHClient(manualSync) defer sshClient.Close() diff --git a/src/sync/commonUNIX.go b/src/sync/commonUNIX.go index bc62632..bc32d4c 100644 --- a/src/sync/commonUNIX.go +++ b/src/sync/commonUNIX.go @@ -24,7 +24,7 @@ func WalkEntryDir() ([]string, []string) { // check for errors encountered while walking directory if err != nil { if os.IsNotExist(err) { - fmt.Println(backend.AnsiError+"The entry directory does not exist - run \""+os.Args[0], "init"+"\" to create it"+backend.AnsiReset) // TODO implement init command for libmuttonserver + fmt.Println(backend.AnsiError+"The entry directory does not exist - run \""+os.Args[0], "init"+"\" to create it"+backend.AnsiReset) } else { // otherwise, print the source of the error fmt.Println(backend.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + backend.AnsiReset) diff --git a/src/sync/commonWIN.go b/src/sync/commonWIN.go index b289ac3..1324153 100644 --- a/src/sync/commonWIN.go +++ b/src/sync/commonWIN.go @@ -25,7 +25,7 @@ func WalkEntryDir() ([]string, []string) { // check for errors encountered while walking directory if err != nil { if os.IsNotExist(err) { - fmt.Println(backend.AnsiError+"The entry directory does not exist - run \""+os.Args[0], "init"+"\" to create it"+backend.AnsiReset) // TODO implement init command for libmuttonserver + fmt.Println(backend.AnsiError+"The entry directory does not exist - run \""+os.Args[0], "init"+"\" to create it"+backend.AnsiReset) } else { // otherwise, print the source of the error fmt.Println(backend.AnsiError + "An unexpected error occurred while generating the entry list: " + err.Error() + backend.AnsiReset)