Switch to dependence on github.com/rwinkhart/libmutton

This commit is contained in:
2024-08-08 17:24:48 -04:00
parent 57378c521a
commit a23ae4a4b9
11 changed files with 114 additions and 110 deletions
+6 -5
View File
@@ -4,12 +4,8 @@ go 1.22.6
require (
github.com/charmbracelet/glamour v0.7.0
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727
github.com/pkg/sftp v1.13.6
github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481
golang.org/x/crypto v0.26.0
github.com/rwinkhart/libmutton v0.0.0-20240808205713-d82188f56655
golang.org/x/term v0.23.0
gopkg.in/ini.v1 v1.67.0
)
require (
@@ -18,6 +14,7 @@ require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/boombuler/barcode v1.0.2 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
@@ -27,10 +24,14 @@ require (
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/sftp v1.13.6 // indirect
github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/yuin/goldmark v1.7.4 // indirect
github.com/yuin/goldmark-emoji v1.0.3 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.23.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
+2
View File
@@ -52,6 +52,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rwinkhart/libmutton v0.0.0-20240808205713-d82188f56655 h1:nlvJc8lkwZcuIWsiIl1qh6g7A/oCLnRz7mnR7oEdlyA=
github.com/rwinkhart/libmutton v0.0.0-20240808205713-d82188f56655/go.mod h1:treJ/J2D6+LuDQQ435Jg3fsXRHlBnoKlMYExo3VzUKc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+10 -10
View File
@@ -7,9 +7,9 @@ import (
"strconv"
"strings"
"github.com/rwinkhart/MUTN/src/backend"
"github.com/rwinkhart/MUTN/src/cli"
"github.com/rwinkhart/MUTN/src/sync"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/sync"
)
func main() {
@@ -55,13 +55,13 @@ func main() {
case "register":
// register a new device ID
// stdin[0] is expected to be the device ID
_, _ = os.Create(backend.ConfigDir + backend.PathSeparator + "devices" + backend.PathSeparator + stdin[0]) // error ignored; failure unlikely to occur if init was successful; "register" is not a user-facing argument and thus the error would not be visible
_, _ = os.Create(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + stdin[0]) // error ignored; failure unlikely to occur if init was successful; "register" is not a user-facing argument and thus the error would not be visible
// print EntryRoot and bool indicating OS type to stdout for client to store in config
fmt.Print(backend.EntryRoot + sync.FSSpace + strconv.FormatBool(backend.IsWindows))
fmt.Print(core.EntryRoot + sync.FSSpace + strconv.FormatBool(core.IsWindows))
case "init":
// create the necessary directories for libmuttonserver to function
backend.DirInit(false)
_ = os.MkdirAll(backend.ConfigDir+backend.PathSeparator+"deletions", 0700) // error ignored; failure would have occurred by this point in backend.DirInit
core.DirInit(false)
_ = os.MkdirAll(core.ConfigDir+core.PathSeparator+"deletions", 0700) // error ignored; failure would have occurred by this point in core.DirInit
fmt.Println("libmuttonserver directories initialized")
case "version":
versionServer()
@@ -71,13 +71,13 @@ func main() {
}
func helpServer() {
fmt.Print(cli.AnsiBold + "\nlibmuttonserver | Copyright (c) 2024 Randall Winkhart\n" + backend.AnsiReset + `
fmt.Print(cli.AnsiBold + "\nlibmuttonserver | Copyright (c) 2024 Randall Winkhart\n" + core.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.
` + cli.AnsiBold + "Usage:" + backend.AnsiReset + ` libmuttonserver <argument>
` + cli.AnsiBold + "Usage:" + core.AnsiReset + ` libmuttonserver <argument>
` + cli.AnsiBold + "Arguments (user):" + backend.AnsiReset + `
` + cli.AnsiBold + "Arguments (user):" + core.AnsiReset + `
help Bring up this menu
version Display version and license information
init Create the necessary directories for libmuttonserver to function` + "\n\n")
@@ -86,7 +86,7 @@ This program comes with absolutely no warranty; type "libmuttonserver version" f
func versionServer() {
cli.MITLicense()
fmt.Print(cli.AnsiBold + "\n\n libmuttonserver" + backend.AnsiReset + " Version " + backend.LibmuttonVersion + `
fmt.Print(cli.AnsiBold + "\n\n libmuttonserver" + core.AnsiReset + " Version " + core.LibmuttonVersion + `
Copyright (c) 2024 Randall Winkhart` + "\n\n")
}
+8 -8
View File
@@ -5,9 +5,9 @@ import (
"os"
"strings"
"github.com/rwinkhart/MUTN/src/backend"
"github.com/rwinkhart/MUTN/src/cli"
"github.com/rwinkhart/MUTN/src/sync"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/sync"
)
func main() {
@@ -25,7 +25,7 @@ func main() {
if strings.HasPrefix(args[1], "/") {
// store location of target entry
targetLocation := backend.TargetLocationFormat(args[1])
targetLocation := core.TargetLocationFormat(args[1])
// entry reader shortcut (if no other arguments are supplied)
if argsCount == 2 {
@@ -38,7 +38,7 @@ func main() {
case "show", "-s":
cli.EntryReaderDecrypt(targetLocation, false)
case "copy":
backend.CopyArgument(args[0], targetLocation, 0)
core.CopyArgument(args[0], targetLocation, 0)
case "edit":
cli.EditEntryField(targetLocation, true, 0)
case "gen":
@@ -82,7 +82,7 @@ func main() {
default:
cli.HelpCopy()
}
backend.CopyArgument(args[0], targetLocation, field)
core.CopyArgument(args[0], targetLocation, field)
case "edit":
var field int // indicates which (numbered) field to edit
switch args[3] {
@@ -97,7 +97,7 @@ func main() {
case "note", "-n":
field = 4
case "rename", "-r":
backend.TargetIsFile(targetLocation, true, 0) // ensure location exists before prompting for new location
core.TargetIsFile(targetLocation, true, 0) // ensure location exists before prompting for new location
cli.RenameCli(args[1]) // pass the incomplete path as the server and all clients (reading from the deletions directory) will have a different home directory
default:
cli.HelpEdit()
@@ -158,13 +158,13 @@ func main() {
} else {
switch args[1] {
case "clipclear":
backend.ClipClearArgument()
core.ClipClearArgument()
case "sync":
sync.RunJob(true)
case "init":
cli.TempInitCli()
case "tweak":
fmt.Println(backend.AnsiError + "\"tweak\" is not yet implemented" + backend.AnsiReset)
fmt.Println(core.AnsiError + "\"tweak\" is not yet implemented" + core.AnsiReset)
os.Exit(0)
case "copy":
cli.HelpCopy()
+5 -5
View File
@@ -5,21 +5,21 @@ import (
"os"
"strings"
"github.com/rwinkhart/MUTN/src/backend"
"github.com/rwinkhart/libmutton/core"
)
// AddEntry creates a new entry at targetLocation by taking user input via CLI prompts
// entryType: 0 = standard (password), 1 = auto-generated password, 2 = note
func AddEntry(targetLocation string, hideSecrets bool, entryType uint8) {
// ensure target location does not already exist
_, isAccessible := backend.TargetIsFile(targetLocation, false, 0)
_, isAccessible := core.TargetIsFile(targetLocation, false, 0)
if isAccessible {
fmt.Println(backend.AnsiError + "Target location already exists" + backend.AnsiReset)
fmt.Println(core.AnsiError + "Target location already exists" + core.AnsiReset)
os.Exit(1)
}
// ensure target containing directory exists and is a directory (not a file)
backend.TargetIsFile(targetLocation[:strings.LastIndex(targetLocation, "/")], true, 1)
core.TargetIsFile(targetLocation[:strings.LastIndex(targetLocation, "/")], true, 1)
var unencryptedEntry []string
@@ -31,7 +31,7 @@ func AddEntry(targetLocation string, hideSecrets bool, entryType uint8) {
if entryType == 0 {
password = inputHidden("Password:")
} else {
password = backend.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2, false)
password = core.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2, false)
}
totp := inputHidden("TOTP secret:")
+11 -11
View File
@@ -8,8 +8,8 @@ import (
"reflect"
"strings"
"github.com/rwinkhart/MUTN/src/backend"
"github.com/rwinkhart/MUTN/src/sync"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/sync"
)
// RenameCli renames an entry at oldLocationIncomplete to a new location (user input) on both the client and the server
@@ -24,7 +24,7 @@ func RenameCli(oldLocationIncomplete string) {
// EditEntryField edits a field of an entry at targetLocation (user input)
func EditEntryField(targetLocation string, hideSecrets bool, field int) {
// fetch old entry data (with all required lines present)
unencryptedEntry := backend.GetOldEntryData(targetLocation, field)
unencryptedEntry := core.GetOldEntryData(targetLocation, field)
// edit the field
switch field {
@@ -44,7 +44,7 @@ func EditEntryField(targetLocation string, hideSecrets bool, field int) {
// edit the note
editedNote, noteEdited := editNote(noteData)
if !noteEdited { // exit early if the note was not edited
fmt.Println(backend.AnsiError + "Entry is unchanged" + backend.AnsiReset)
fmt.Println(core.AnsiError + "Entry is unchanged" + core.AnsiReset)
os.Exit(1)
}
unencryptedEntry = append(nonNoteData, editedNote...)
@@ -57,10 +57,10 @@ func EditEntryField(targetLocation string, hideSecrets bool, field int) {
// GenUpdate generates a new password for an entry at targetLocation (user input)
func GenUpdate(targetLocation string, hideSecrets bool) {
// fetch old entry data
unencryptedEntry := backend.GetOldEntryData(targetLocation, 0)
unencryptedEntry := core.GetOldEntryData(targetLocation, 0)
// generate a new password
unencryptedEntry[0] = backend.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2, false)
unencryptedEntry[0] = core.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2, false)
// write and preview the modified entry
writeEntryCLI(targetLocation, unencryptedEntry, hideSecrets, false)
@@ -69,13 +69,13 @@ func GenUpdate(targetLocation string, hideSecrets bool) {
// editNote uses the user-specified text editor to edit an existing note (or create a new one if baseNote is empty)
// returns the edited note and a boolean indicating whether the note was edited
func editNote(baseNote []string) ([]string, bool) {
tempFile := backend.CreateTempFile()
tempFile := core.CreateTempFile()
defer func(name string) {
_ = os.Remove(name) // error ignored; if the file could be created, it can probably be removed
}(tempFile.Name())
// fetch the user's text editor
editor := backend.ParseConfig([][2]string{{"MUTN", "textEditor"}}, "")[0]
editor := core.ParseConfig([][2]string{{"MUTN", "textEditor"}}, "")[0]
// write baseNote to tempFile (if it is not empty)
if len(baseNote) > 0 {
@@ -94,13 +94,13 @@ func editNote(baseNote []string) ([]string, bool) {
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
panic(backend.AnsiError + "Failed to write note with " + editor + backend.AnsiReset) // panic is used to ensure the tempFile is removed, as per the defer statement
panic(core.AnsiError + "Failed to write note with " + editor + core.AnsiReset) // panic is used to ensure the tempFile is removed, as per the defer statement
}
// open the tempFile for reading
tempFile, err = os.Open(tempFile.Name())
if err != nil {
panic(backend.AnsiError + "Failed to read note written with " + editor + backend.AnsiReset) // panic is used to ensure the tempFile is removed, as per the defer statement
panic(core.AnsiError + "Failed to read note written with " + editor + core.AnsiReset) // panic is used to ensure the tempFile is removed, as per the defer statement
}
// read the edited note from the tempFile
@@ -114,7 +114,7 @@ func editNote(baseNote []string) ([]string, bool) {
_ = tempFile.Close() // error ignored; if the file could be opened, it can probably be closed
// remove trailing empty strings from the edited note
note = backend.RemoveTrailingEmptyStrings(note)
note = core.RemoveTrailingEmptyStrings(note)
// trim trailing whitespace from each note line
for i, line := range note {
+8 -7
View File
@@ -2,10 +2,11 @@ package cli
import (
"fmt"
"github.com/rwinkhart/MUTN/src/backend"
"github.com/rwinkhart/MUTN/src/sync"
"os"
"strings"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/sync"
)
// global constants used only in this file
@@ -71,7 +72,7 @@ func printFileEntry(entry string, lastSlash, charCounter, indent int, colorAlter
}
// print fileEntryName to screen
fmt.Printf("%s%s%s ", colorCode, fileEntryName, backend.AnsiReset)
fmt.Printf("%s%s%s ", colorCode, fileEntryName, core.AnsiReset)
return charCounter, colorAlternator
}
@@ -81,7 +82,7 @@ func EntryListGen() {
fileList, dirList := sync.WalkEntryDir()
// print header bar w/total entry count
fmt.Print("\n"+ansiBlackOnWhite, len(fileList), " libmutton entries:"+backend.AnsiReset)
fmt.Print("\n"+ansiBlackOnWhite, len(fileList), " libmutton entries:"+core.AnsiReset)
// dirList iteration
dirListLength := len(dirList) // save length for multiple references below
@@ -127,7 +128,7 @@ func EntryListGen() {
containsFiles = true
skippedDirList[i] = false // the directory header is being printed, indicate that it is not being skipped
indent, vanityDirectory = determineIndentation(skippedDirList, dirList, i) // calculate the final indentation multiplier
fmt.Printf("\n\n"+strings.Repeat(" ", indent*2)+ansiDirectoryHeader+"%s/"+backend.AnsiReset+"\n", vanityDirectory)
fmt.Printf("\n\n"+strings.Repeat(" ", indent*2)+ansiDirectoryHeader+"%s/"+core.AnsiReset+"\n", vanityDirectory)
}
charCounter, colorAlternator = printFileEntry(file, lastSlash, charCounter, indent, colorAlternator)
@@ -139,8 +140,8 @@ func EntryListGen() {
if dirListLength > 1 { // and directories besides the root-level exist... display directory header and empty directory warning
skippedDirList[i] = false // the directory header is being printed, indicate that it is not being skipped
indent, vanityDirectory = determineIndentation(skippedDirList, dirList, i) // calculate the final indentation multiplier
fmt.Printf("\n\n"+strings.Repeat(" ", indent*2)+ansiDirectoryHeader+"%s/"+backend.AnsiReset+"\n", vanityDirectory)
fmt.Print(strings.Repeat(" ", indent*2) + ansiEmptyDirectoryWarning + "-empty directory-" + backend.AnsiReset)
fmt.Printf("\n\n"+strings.Repeat(" ", indent*2)+ansiDirectoryHeader+"%s/"+core.AnsiReset+"\n", vanityDirectory)
fmt.Print(strings.Repeat(" ", indent*2) + ansiEmptyDirectoryWarning + "-empty directory-" + core.AnsiReset)
} else { // warn if the only thing that exists is the root-level directory
fmt.Print("\n\nNothing's here! For help creating your first entry, run \"mutn help\".")
}
+11 -11
View File
@@ -5,8 +5,8 @@ import (
"os"
"strings"
"github.com/rwinkhart/MUTN/src/backend"
"github.com/rwinkhart/MUTN/src/sync"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/sync"
)
const ansiShownPassword = "\033[38;5;10m"
@@ -21,33 +21,33 @@ func EntryReader(decryptedEntry []string, hideSecrets, syncEnabled bool) {
// if the first field (password) is not empty, print it
if decryptedEntry[0] != "" {
if !hideSecrets {
fmt.Print(ansiDirectoryHeader + "Password:" + backend.AnsiReset + "\n" + ansiShownPassword + decryptedEntry[0] + backend.AnsiReset + "\n\n")
fmt.Print(ansiDirectoryHeader + "Password:" + core.AnsiReset + "\n" + ansiShownPassword + decryptedEntry[0] + core.AnsiReset + "\n\n")
} else {
fmt.Print(ansiDirectoryHeader + "Password:" + backend.AnsiReset + "\n" + ansiEmptyDirectoryWarning + "End command in \"show\" or \"-s\" to view" + backend.AnsiReset + "\n\n")
fmt.Print(ansiDirectoryHeader + "Password:" + core.AnsiReset + "\n" + ansiEmptyDirectoryWarning + "End command in \"show\" or \"-s\" to view" + core.AnsiReset + "\n\n")
}
}
case 1:
// if the second field (username) is not empty, print it
if decryptedEntry[1] != "" {
fmt.Print(ansiDirectoryHeader + "Username:" + backend.AnsiReset + "\n" + decryptedEntry[1] + "\n\n")
fmt.Print(ansiDirectoryHeader + "Username:" + core.AnsiReset + "\n" + decryptedEntry[1] + "\n\n")
}
case 2:
// if the third field (TOTP secret) is not empty, print it
if decryptedEntry[2] != "" {
if !hideSecrets {
fmt.Print(ansiDirectoryHeader + "TOTP Secret:" + backend.AnsiReset + "\n" + ansiShownPassword + decryptedEntry[2] + backend.AnsiReset + "\n\n")
fmt.Print(ansiDirectoryHeader + "TOTP Secret:" + core.AnsiReset + "\n" + ansiShownPassword + decryptedEntry[2] + core.AnsiReset + "\n\n")
} else {
fmt.Print(ansiDirectoryHeader + "TOTP Secret:" + backend.AnsiReset + "\n" + ansiEmptyDirectoryWarning + "End command in \"show\" or \"-s\" to view" + backend.AnsiReset + "\n\n")
fmt.Print(ansiDirectoryHeader + "TOTP Secret:" + core.AnsiReset + "\n" + ansiEmptyDirectoryWarning + "End command in \"show\" or \"-s\" to view" + core.AnsiReset + "\n\n")
}
}
case 3:
// if the fourth field (url) is not empty, print it
if decryptedEntry[3] != "" {
fmt.Print(ansiDirectoryHeader + "URL:" + backend.AnsiReset + "\n" + decryptedEntry[3] + "\n\n")
fmt.Print(ansiDirectoryHeader + "URL:" + core.AnsiReset + "\n" + decryptedEntry[3] + "\n\n")
}
case 4:
// print the notes header
fmt.Println(ansiDirectoryHeader + "Notes:" + backend.AnsiReset)
fmt.Println(ansiDirectoryHeader + "Notes:" + core.AnsiReset)
// combine remaining fields into a single string (to support Markdown rendering)
var notesSlice []string
@@ -73,8 +73,8 @@ func EntryReader(decryptedEntry []string, hideSecrets, syncEnabled bool) {
// EntryReaderDecrypt is a wrapper for EntryReader that first decrypts a GPG-encrypted file before sending it to EntryReader
func EntryReaderDecrypt(targetLocation string, hideSecrets bool) {
if isFile, _ := backend.TargetIsFile(targetLocation, true, 2); isFile {
EntryReader(backend.DecryptGPG(targetLocation), hideSecrets, false) // never sync if decrypting straight to EntryReader, as this means the entry could not have been modified
if isFile, _ := core.TargetIsFile(targetLocation, true, 2); isFile {
EntryReader(core.DecryptGPG(targetLocation), hideSecrets, false) // never sync if decrypting straight to EntryReader, as this means the entry could not have been modified
}
// do not exit, as this is the job of EntryReader
}
+14 -14
View File
@@ -6,8 +6,8 @@ import (
"os"
"strconv"
"github.com/rwinkhart/MUTN/src/backend"
"github.com/rwinkhart/MUTN/src/sync"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/sync"
)
// TempInitCli initializes the MUTN environment based on user input (will be replaced with a TUI menu)
@@ -15,13 +15,13 @@ func TempInitCli() {
// gpgID
var gpgID string
if inputBinary("Auto-generate GPG key?") {
gpgID = backend.GpgKeyGen()
gpgID = core.GpgKeyGen()
} else {
// select GPG key from menu
uidSlice := backend.GpgUIDListGen()
uidSlice := core.GpgUIDListGen()
gpgIDInt := inputMenuGen("Select GPG key:", uidSlice)
if gpgIDInt == 0 {
fmt.Println(backend.AnsiError + "No GPG keys found - please generate one" + backend.AnsiReset)
fmt.Println(core.AnsiError + "No GPG keys found - please generate one" + core.AnsiReset)
os.Exit(1)
}
gpgID = uidSlice[gpgIDInt-1]
@@ -34,7 +34,7 @@ func TempInitCli() {
configSSH := inputBinary("Configure SSH settings (for synchronization)?")
if configSSH {
// necessary SSH info
fmt.Println(AnsiBold + "\nNote:" + backend.AnsiReset + " Only key-based authentication is supported (keys may optionally be passphrase-protected).\nThe remote server must already be in your ~" + backend.PathSeparator + ".ssh" + backend.PathSeparator + "known_hosts file.")
fmt.Println(AnsiBold + "\nNote:" + core.AnsiReset + " Only key-based authentication is supported (keys may optionally be passphrase-protected).\nThe remote server must already be in your ~" + core.PathSeparator + ".ssh" + core.PathSeparator + "known_hosts file.")
sshUser := input("Remote SSH username:")
sshPort := input("Remote SSH port:")
sshIP := input("Remote SSH IP/domain:")
@@ -43,32 +43,32 @@ func TempInitCli() {
var sshKey string
var sshKeyIsFile bool
for !sshKeyIsFile {
fallbackSSHKey := backend.Home + backend.PathSeparator + ".ssh" + backend.PathSeparator + "id_ed25519"
fallbackSSHKey := core.Home + core.PathSeparator + ".ssh" + core.PathSeparator + "id_ed25519"
sshKey = cmp.Or(expandPathWithHome(input("SSH private identity file path (falls back to \""+fallbackSSHKey+"\"):")), fallbackSSHKey)
sshKeyIsFile, _ = backend.TargetIsFile(sshKey, false, 0)
sshKeyIsFile, _ = core.TargetIsFile(sshKey, false, 0)
if !sshKeyIsFile {
fmt.Println(backend.AnsiError + "SSH identity file not found: " + sshKey + backend.AnsiReset)
fmt.Println(core.AnsiError + "SSH identity file not found: " + sshKey + core.AnsiReset)
}
}
sshKeyProtected := inputBinary("Is the identity file password-protected?")
// initialize libmutton directories
backend.DirInit(false)
core.DirInit(false)
// write config file (temporarily assigns sshEntryRoot and sshIsWindows to null to pass initial device ID registration)
backend.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}, {"LIBMUTTON", "sshUser", sshUser}, {"LIBMUTTON", "sshIP", sshIP}, {"LIBMUTTON", "sshPort", sshPort}, {"LIBMUTTON", "sshKey", sshKey}, {"LIBMUTTON", "sshKeyProtected", strconv.FormatBool(sshKeyProtected)}, {"LIBMUTTON", "sshEntryRoot", "null"}, {"LIBMUTTON", "sshIsWindows", "null"}}, false)
core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}, {"LIBMUTTON", "sshUser", sshUser}, {"LIBMUTTON", "sshIP", sshIP}, {"LIBMUTTON", "sshPort", sshPort}, {"LIBMUTTON", "sshKey", sshKey}, {"LIBMUTTON", "sshKeyProtected", strconv.FormatBool(sshKeyProtected)}, {"LIBMUTTON", "sshEntryRoot", "null"}, {"LIBMUTTON", "sshIsWindows", "null"}}, false)
// generate and register device ID
sshEntryRoot, sshIsWindows := sync.DeviceIDGen()
// update config file with sshEntryRoot and sshIsWindows
backend.WriteConfig([][3]string{{"LIBMUTTON", "sshEntryRoot", sshEntryRoot}, {"LIBMUTTON", "sshIsWindows", sshIsWindows}}, true)
core.WriteConfig([][3]string{{"LIBMUTTON", "sshEntryRoot", sshEntryRoot}, {"LIBMUTTON", "sshIsWindows", sshIsWindows}}, true)
} else {
// initialize libmutton directories
backend.DirInit(false)
core.DirInit(false)
// write config file
backend.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}}, false)
core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}}, false)
}
}
+30 -30
View File
@@ -4,7 +4,7 @@ import (
"fmt"
"os"
"github.com/rwinkhart/MUTN/src/backend"
"github.com/rwinkhart/libmutton/core"
)
// global constants used only in this file
@@ -14,13 +14,13 @@ const (
)
func HelpMain() {
fmt.Print(AnsiBold + "\nMUTN | Copyright (c) 2024 Randall Winkhart\n" + backend.AnsiReset + `
fmt.Print(AnsiBold + "\nMUTN | Copyright (c) 2024 Randall Winkhart\n" + core.AnsiReset + `
This software exists under the MIT license; you may redistribute it under certain conditions.
This program comes with absolutely no warranty; type "mutn version" for details.
` + AnsiBold + "Usage:" + backend.AnsiReset + ` mutn [/<entry name> [argument] [option]] | [argument]
` + AnsiBold + "Usage:" + core.AnsiReset + ` mutn [/<entry name> [argument] [option]] | [argument]
` + AnsiBold + "Arguments:" + backend.AnsiReset + `
` + AnsiBold + "Arguments:" + core.AnsiReset + `
help Bring up this menu
version Display version and license information
init Set up MUTN (generates libmutton.ini)
@@ -31,7 +31,7 @@ This program comes with absolutely no warranty; type "mutn version" for details.
shear Delete an existing entry
sync Manually sync the entry directory
` + AnsiBold + "Options:" + backend.AnsiReset + `
` + AnsiBold + "Options:" + core.AnsiReset + `
copy:
password|-pw|<blank> Copy the password in an entry to your clipboard
username|-u Copy the username in an entry to your clipboard
@@ -52,17 +52,17 @@ This program comes with absolutely no warranty; type "mutn version" for details.
note|-n Add a note entry
folder|-f Add a new folder for entries
` + AnsiBold + "Tip 1:" + backend.AnsiReset + ` You can quickly read an entry with "mutn /<entry name>"
` + AnsiBold + "Tip 2:" + backend.AnsiReset + ` Type "mutn" (no arguments/options) to view a list of saved entries
` + AnsiBold + "Tip 3:" + backend.AnsiReset + ` Provide "add", "edit", "copy", or "gen" as the only argument to receive more specific help
` + AnsiBold + "Tip 4:" + backend.AnsiReset + " Using \"add\", \"edit\", or \"copy\" without specifying an option (field) will default to \"password\"\n\n")
` + AnsiBold + "Tip 1:" + core.AnsiReset + ` You can quickly read an entry with "mutn /<entry name>"
` + AnsiBold + "Tip 2:" + core.AnsiReset + ` Type "mutn" (no arguments/options) to view a list of saved entries
` + AnsiBold + "Tip 3:" + core.AnsiReset + ` Provide "add", "edit", "copy", or "gen" as the only argument to receive more specific help
` + AnsiBold + "Tip 4:" + core.AnsiReset + " Using \"add\", \"edit\", or \"copy\" without specifying an option (field) will default to \"password\"\n\n")
os.Exit(0)
}
func HelpAdd() {
fmt.Print(AnsiBold + "\nUsage:" + backend.AnsiReset + ` mutn /<entry name> add <option>
fmt.Print(AnsiBold + "\nUsage:" + core.AnsiReset + ` mutn /<entry name> add <option>
` + AnsiBold + "Options:" + backend.AnsiReset + `
` + AnsiBold + "Options:" + core.AnsiReset + `
add:
password|-pw|<blank> Add a password entry
note|-n Add a note entry
@@ -71,9 +71,9 @@ func HelpAdd() {
}
func HelpEdit() {
fmt.Print(AnsiBold + "\nUsage:" + backend.AnsiReset + ` mutn /<entry name> edit <option>
fmt.Print(AnsiBold + "\nUsage:" + core.AnsiReset + ` mutn /<entry name> edit <option>
` + AnsiBold + "Options:" + backend.AnsiReset + `
` + AnsiBold + "Options:" + core.AnsiReset + `
edit:
password|-pw|<blank> Change the password in an entry
username|-u Change the username in an entry
@@ -85,9 +85,9 @@ func HelpEdit() {
}
func HelpCopy() {
fmt.Print(AnsiBold + "\nUsage:" + backend.AnsiReset + ` mutn /<entry name> copy <option>
fmt.Print(AnsiBold + "\nUsage:" + core.AnsiReset + ` mutn /<entry name> copy <option>
` + AnsiBold + "Options:" + backend.AnsiReset + `
` + AnsiBold + "Options:" + core.AnsiReset + `
copy:
password|-pw|<blank> Copy the password in an entry to your clipboard
username|-u Copy the username in an entry to your clipboard
@@ -98,18 +98,18 @@ func HelpCopy() {
}
func HelpGen() {
fmt.Print(AnsiBold + "\nUsage:" + backend.AnsiReset + ` mutn /<entry name> gen [option]
fmt.Print(AnsiBold + "\nUsage:" + core.AnsiReset + ` mutn /<entry name> gen [option]
` + AnsiBold + "Options:" + backend.AnsiReset + `
` + AnsiBold + "Options:" + core.AnsiReset + `
gen:
update|-u Generate a password for an existing entry
` + AnsiBold + "Tip:" + backend.AnsiReset + " If no options are provided, a new password entry is generated\n\n")
` + AnsiBold + "Tip:" + core.AnsiReset + " If no options are provided, a new password entry is generated\n\n")
os.Exit(0)
}
func MITLicense() {
fmt.Print(AnsiBold + "\n MIT License" + backend.AnsiReset + `
fmt.Print(AnsiBold + "\n MIT License" + core.AnsiReset + `
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
@@ -141,18 +141,18 @@ func Version() {
MITLicense()
fmt.Print("\n\n MUTN is a simple, self-hosted,\n SSH-synchronized password manager based on libmutton\n\n" +
" .. ..\n" +
" /()\\''.''. " + ansiVersionMeat + "♥♥♥♥" + backend.AnsiReset + " .''.''/()\\ _)\n" +
" _. : * " + ansiVersionMeat + "♥♥♥♥♥♥ ♥♥♥♥♥♥♥♥" + backend.AnsiReset + " * : <[◎]|_|=\n" +
" }-}-*] `..'..' " + ansiVersionMeat + "♥♥♥♥♥♥♥♥♥♥♥♥♥" + backend.AnsiReset + " `..'..' |\n" +
" ◎-◎ // \\\\ " + ansiVersionMeat + "♥♥♥♥♥♥♥♥♥" + backend.AnsiReset + " // \\\\ /|\\\n" +
" /()\\''.''. " + ansiVersionMeat + "♥♥♥♥" + core.AnsiReset + " .''.''/()\\ _)\n" +
" _. : * " + ansiVersionMeat + "♥♥♥♥♥♥ ♥♥♥♥♥♥♥♥" + core.AnsiReset + " * : <[◎]|_|=\n" +
" }-}-*] `..'..' " + ansiVersionMeat + "♥♥♥♥♥♥♥♥♥♥♥♥♥" + core.AnsiReset + " `..'..' |\n" +
" ◎-◎ // \\\\ " + ansiVersionMeat + "♥♥♥♥♥♥♥♥♥" + core.AnsiReset + " // \\\\ /|\\\n" +
ansiVersionOutline + "<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" +
"\\" + ansiBlackOnWhite + " " + backend.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " MUTN Version " + backend.LibmuttonVersion + " " + backend.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " The Tripe Transmission Update " + backend.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " " + backend.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " Copyright (c) 2024 Randall Winkhart " + backend.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " " + backend.AnsiReset + ansiVersionOutline + "/\n" +
"<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" + backend.AnsiReset +
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " MUTN Version " + core.LibmuttonVersion + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " The Tripe Transmission Update " + core.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " Copyright (c) 2024 Randall Winkhart " + core.AnsiReset + ansiVersionOutline + "/\n" +
"\\" + ansiBlackOnWhite + " " + core.AnsiReset + ansiVersionOutline + "/\n" +
"<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" + core.AnsiReset +
"\n For more information, see:\n\n" +
" https://github.com/rwinkhart/MUTN\n" +
" https://github.com/rwinkhart/libmutton\n\n")
+8 -8
View File
@@ -6,8 +6,8 @@ import (
"os"
"strings"
"github.com/rwinkhart/MUTN/src/backend"
"golang.org/x/crypto/ssh/terminal"
"github.com/rwinkhart/libmutton/core"
"golang.org/x/term"
)
// input prompts the user for input and returns the input as a string
@@ -21,7 +21,7 @@ func input(prompt string) string {
// inputHidden prompts the user for input and returns the input as a string, hiding the input from the terminal
func inputHidden(prompt string) string {
fmt.Print("\n" + prompt + " ")
byteInput, _ := terminal.ReadPassword(int(os.Stdin.Fd()))
byteInput, _ := term.ReadPassword(int(os.Stdin.Fd()))
password := string(byteInput)
fmt.Println()
return password
@@ -66,19 +66,19 @@ func inputMenuGen(prompt string, options []string) int {
// writeEntryCLI writes an entry to targetLocation and previews it (errors if no data is supplied)
func writeEntryCLI(targetLocation string, unencryptedEntry []string, hideSecrets, verifyEntryDoesNotExist bool) {
if backend.EntryIsNotEmpty(unencryptedEntry) {
if core.EntryIsNotEmpty(unencryptedEntry) {
// write the entry to the target location
backend.WriteEntry(targetLocation, unencryptedEntry, verifyEntryDoesNotExist)
core.WriteEntry(targetLocation, unencryptedEntry, verifyEntryDoesNotExist)
// preview the entry
fmt.Println(AnsiBold + "\nEntry Preview:" + backend.AnsiReset)
fmt.Println(AnsiBold + "\nEntry Preview:" + core.AnsiReset)
EntryReader(unencryptedEntry, hideSecrets, true)
} else {
fmt.Println(backend.AnsiError + "No data supplied for entry" + backend.AnsiReset)
fmt.Println(core.AnsiError + "No data supplied for entry" + core.AnsiReset)
os.Exit(1)
}
}
// 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, "~", backend.Home, 1)
return strings.Replace(path, "~", core.Home, 1)
}