Port to latest libmutton development version

This commit is contained in:
2025-05-29 23:38:23 +00:00
parent 9cd248eb8b
commit 2ab8eac7a4
8 changed files with 49 additions and 103 deletions
+2 -2
View File
@@ -5,8 +5,8 @@ go 1.24.3
require ( require (
github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514 github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514
github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/glamour v0.7.0
github.com/rwinkhart/go-boilerplate v0.0.0-20250509173525-20670ec7bb9c github.com/rwinkhart/go-boilerplate v0.0.0-20250529185306-e2e64d7fa43b
github.com/rwinkhart/libmutton v0.3.2-0.20250521151921-02c663447a55 github.com/rwinkhart/libmutton v0.3.2-0.20250529233550-74b8261bc88f
golang.org/x/term v0.32.0 golang.org/x/term v0.32.0
) )
+4 -4
View File
@@ -57,12 +57,12 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rwinkhart/convertroman v0.2.0 h1:otUm939eXT77q/Lr31mJtMwVWEw0RjEUB71gO65h9OM= github.com/rwinkhart/convertroman v0.2.0 h1:otUm939eXT77q/Lr31mJtMwVWEw0RjEUB71gO65h9OM=
github.com/rwinkhart/convertroman v0.2.0/go.mod h1:Af6HqvX0EIM4Y3HcnNXbbRey1dLasGB7WU0+3Cowgmw= github.com/rwinkhart/convertroman v0.2.0/go.mod h1:Af6HqvX0EIM4Y3HcnNXbbRey1dLasGB7WU0+3Cowgmw=
github.com/rwinkhart/go-boilerplate v0.0.0-20250509173525-20670ec7bb9c h1:RIMnYf1MwsvmAr9E0/cpn7rTh8BWdfsQ2r31ITKJp2A= github.com/rwinkhart/go-boilerplate v0.0.0-20250529185306-e2e64d7fa43b h1:ENgsUlCmYktd1eauEkjW6Fu8rgZzyGOS/m/6jc968xI=
github.com/rwinkhart/go-boilerplate v0.0.0-20250509173525-20670ec7bb9c/go.mod h1:cnzIF45I0FCOvE4YIB+26pLCUx2kWyY2llKYZruNaRY= github.com/rwinkhart/go-boilerplate v0.0.0-20250529185306-e2e64d7fa43b/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 h1:NhHwFM3Pgm6zRUfFKvi0p5ndjfFbVWsRwmmhyFlG4PE=
github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/rwinkhart/libmutton v0.3.2-0.20250521151921-02c663447a55 h1:eYUJ/h+lN6O/ovrrSM9vLidWAmRiViLyeMRl5P0DD4Q= github.com/rwinkhart/libmutton v0.3.2-0.20250529233550-74b8261bc88f h1:HL3z/oTAqssmILfA3Z0IAscK1sbxFp8/g9GnhHSNLSY=
github.com/rwinkhart/libmutton v0.3.2-0.20250521151921-02c663447a55/go.mod h1:noIP2Ov56fNq2McVhkOj5+VVLOV4E2eie2HbuOhXhXA= github.com/rwinkhart/libmutton v0.3.2-0.20250529233550-74b8261bc88f/go.mod h1:uIz4AJcXW7ZHvvoPlLNWQofTBIbFa1UJ2JI6O+FSQ60=
github.com/rwinkhart/peercred-mini v0.1.0 h1:TiS6u8cEWzW55S9X4iVpU72Iuy/NG6rMUJMtUEVEFLw= github.com/rwinkhart/peercred-mini v0.1.0 h1:TiS6u8cEWzW55S9X4iVpU72Iuy/NG6rMUJMtUEVEFLw=
github.com/rwinkhart/peercred-mini v0.1.0/go.mod h1:+x4Mxc2veE+YePSOpcUasjimh7n799c1KraLuQwHR20= github.com/rwinkhart/peercred-mini v0.1.0/go.mod h1:+x4Mxc2veE+YePSOpcUasjimh7n799c1KraLuQwHR20=
github.com/rwinkhart/rcw v0.2.0 h1:/rFgJ+20OSThipKAX5KPwW2xbPpgh1zl49THmEkkPis= github.com/rwinkhart/rcw v0.2.0 h1:/rFgJ+20OSThipKAX5KPwW2xbPpgh1zl49THmEkkPis=
+20 -1
View File
@@ -1,11 +1,15 @@
package main package main
import ( import (
"bytes"
"cmp"
"fmt"
"os" "os"
"strings" "strings"
"github.com/rwinkhart/MUTN/src/cli" "github.com/rwinkhart/MUTN/src/cli"
"github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/go-boilerplate/front"
"github.com/rwinkhart/libmutton/core" "github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/crypt" "github.com/rwinkhart/libmutton/crypt"
"github.com/rwinkhart/libmutton/global" "github.com/rwinkhart/libmutton/global"
@@ -166,7 +170,22 @@ func main() {
case "sync": case "sync":
syncclient.RunJob(true, false) syncclient.RunJob(true, false)
case "init": case "init":
cli.TempInitCli() const fallbackEditor = "vi" // TODO "edit" for Windows
var rcwPass []byte
for { // get master passphrase
rcwPass = front.InputHidden("Master passphrase:")
if !bytes.Equal(rcwPass, front.InputHidden("Confirm master passphrase:")) || len(rcwPass) == 0 {
fmt.Println(back.AnsiError + "Passphrases do not match" + back.AnsiReset)
continue
}
break
}
err := core.LibmuttonInit(front.Input,
[][3]string{{"MUTN", "textEditor", cmp.Or(front.Input("Text editor (leave blank for $EDITOR, falls back to \""+fallbackEditor+"\"):"), os.Getenv("EDITOR"), fallbackEditor)}},
rcwPass, false)
if err != nil {
back.PrintError("Initialization failed: "+err.Error(), 0, true)
}
case "tweak": case "tweak":
back.PrintError("\"tweak\" is not yet implemented", 0, true) back.PrintError("\"tweak\" is not yet implemented", 0, true)
case "copy": case "copy":
-1
View File
@@ -15,7 +15,6 @@ var (
const ( const (
MUTNVersion = "0.D.0" // untagged releases feature a letter suffix corresponding to the eventual release version, e.g "0.2.A" -> "0.2.0", "0.2.B" -> "0.2.1" MUTNVersion = "0.D.0" // untagged releases feature a letter suffix corresponding to the eventual release version, e.g "0.2.A" -> "0.2.0", "0.2.B" -> "0.2.1"
AnsiBold = "\033[1m"
ansiBlackOnWhite = "\033[38;5;0;48;5;15m" ansiBlackOnWhite = "\033[38;5;0;48;5;15m"
) )
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/go-boilerplate/front" "github.com/rwinkhart/go-boilerplate/front"
"github.com/rwinkhart/libmutton/cfg"
"github.com/rwinkhart/libmutton/core" "github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/syncclient" "github.com/rwinkhart/libmutton/syncclient"
) )
@@ -74,7 +75,7 @@ func editNote(baseNote []string) ([]string, bool) {
}(tempFile.Name()) }(tempFile.Name())
// fetch the user's text editor // fetch the user's text editor
editorCfg, _ := core.ParseConfig([][2]string{{"MUTN", "textEditor"}}, "") editorCfg, _ := cfg.ParseConfig([][2]string{{"MUTN", "textEditor"}}, "")
editor := editorCfg[0] editor := editorCfg[0]
// write baseNote to tempFile (if it is not empty) // write baseNote to tempFile (if it is not empty)
-74
View File
@@ -1,74 +0,0 @@
package cli
import (
"bytes"
"cmp"
"fmt"
"os"
"strconv"
"github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/go-boilerplate/front"
"github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/global"
"github.com/rwinkhart/libmutton/syncclient"
)
// TempInitCli initializes the MUTN environment based on user input.
func TempInitCli() {
// text editor
textEditor := cmp.Or(front.Input("Text editor (leave blank for $EDITOR, falls back to \""+fallbackEditor+"\"):"), os.Getenv("EDITOR"), fallbackEditor)
// SSH info
configSSH := front.InputBinary("Configure SSH settings (for synchronization)?")
if configSSH {
// necessary SSH info
fmt.Println(AnsiBold + "\nNote:" + back.AnsiReset + " Only key-based authentication is supported (keys may optionally be passphrase-protected).\nThe remote server must already be in your ~" + global.PathSeparator + ".ssh" + global.PathSeparator + "known_hosts file.")
sshUser := front.Input("Remote SSH username:")
sshPort := front.Input("Remote SSH port:")
sshIP := front.Input("Remote SSH IP/domain:")
// prompt for and ensure existence of SSH identity file
var sshKey string
var sshKeyIsFile bool
for !sshKeyIsFile {
fallbackSSHKey := back.Home + global.PathSeparator + ".ssh" + global.PathSeparator + "id_ed25519"
sshKey = cmp.Or(back.ExpandPathWithHome(front.Input("SSH private identity file path (falls back to \""+fallbackSSHKey+"\"):")), fallbackSSHKey)
sshKeyIsFile, _ = back.TargetIsFile(sshKey, false, 0)
if !sshKeyIsFile {
fmt.Println(back.AnsiError+"SSH identity file not found:", sshKey+back.AnsiReset) // do not exit after error (allow user to retry)
}
}
sshKeyProtected := front.InputBinary("Is the identity file password-protected?")
// initialize libmutton directories
oldDeviceID := global.DirInit(false)
// write config file (temporarily assigns sshEntryRoot and sshIsWindows to null to pass initial device ID registration)
core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "sshUser", sshUser}, {"LIBMUTTON", "sshIP", sshIP}, {"LIBMUTTON", "sshPort", sshPort}, {"LIBMUTTON", "sshKey", sshKey}, {"LIBMUTTON", "sshKeyProtected", strconv.FormatBool(sshKeyProtected)}, {"LIBMUTTON", "sshEntryRoot", "null"}, {"LIBMUTTON", "sshIsWindows", "false"}}, nil, false)
// generate and register device ID
sshEntryRoot, sshIsWindows := syncclient.DeviceIDGen(oldDeviceID)
// update config file with sshEntryRoot and sshIsWindows
core.WriteConfig([][3]string{{"LIBMUTTON", "sshEntryRoot", sshEntryRoot}, {"LIBMUTTON", "sshIsWindows", sshIsWindows}}, nil, true)
} else {
// initialize libmutton directories
global.DirInit(false)
// write config file
core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}}, nil, false)
}
// RCW sanity check file
var passphrase []byte
for {
passphrase = front.InputHidden("Master passphrase:")
if !bytes.Equal(passphrase, front.InputHidden("Confirm master passphrase:")) {
fmt.Println(back.AnsiError + "Passphrases do not match" + back.AnsiReset)
continue
}
core.RCWSanityCheckGen(passphrase)
break
}
}
+18 -18
View File
@@ -15,13 +15,13 @@ const (
) )
func HelpMain() { func HelpMain() {
fmt.Print(AnsiBold + "\nMUTN | Copyright (c) 2024-2025 Randall Winkhart\n" + back.AnsiReset + ` fmt.Print(back.AnsiBold + "\nMUTN | Copyright (c) 2024-2025 Randall Winkhart\n" + back.AnsiReset + `
This software exists under the MIT license; you may redistribute it under certain conditions. 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. This program comes with absolutely no warranty; type "mutn version" for details.
` + AnsiBold + "Usage:" + back.AnsiReset + ` mutn [/<entry name> [argument] [option]] | [argument] ` + back.AnsiBold + "Usage:" + back.AnsiReset + ` mutn [/<entry name> [argument] [option]] | [argument]
` + AnsiBold + "Arguments:" + back.AnsiReset + ` ` + back.AnsiBold + "Arguments:" + back.AnsiReset + `
help Bring up this menu help Bring up this menu
version Display version and license information version Display version and license information
init Set up MUTN (generates libmutton.ini) init Set up MUTN (generates libmutton.ini)
@@ -32,7 +32,7 @@ This program comes with absolutely no warranty; type "mutn version" for details.
shear Delete an existing entry shear Delete an existing entry
sync Manually sync the entry directory sync Manually sync the entry directory
` + AnsiBold + "Options:" + back.AnsiReset + ` ` + back.AnsiBold + "Options:" + back.AnsiReset + `
copy: copy:
password|-pw|<blank> Copy the password in an entry to your clipboard password|-pw|<blank> Copy the password in an entry to your clipboard
username|-u Copy the username in an entry to your clipboard username|-u Copy the username in an entry to your clipboard
@@ -53,17 +53,17 @@ This program comes with absolutely no warranty; type "mutn version" for details.
note|-n Add a note entry note|-n Add a note entry
folder|-f Add a new folder for entries folder|-f Add a new folder for entries
` + AnsiBold + "Tip 1:" + back.AnsiReset + ` You can quickly read an entry with "mutn /<entry name>" ` + back.AnsiBold + "Tip 1:" + back.AnsiReset + ` You can quickly read an entry with "mutn /<entry name>"
` + AnsiBold + "Tip 2:" + back.AnsiReset + ` Type "mutn" (no arguments/options) to view a list of saved entries ` + back.AnsiBold + "Tip 2:" + back.AnsiReset + ` Type "mutn" (no arguments/options) to view a list of saved entries
` + AnsiBold + "Tip 3:" + back.AnsiReset + ` Provide "add", "edit", "copy", or "gen" as the only argument to receive more specific help ` + back.AnsiBold + "Tip 3:" + back.AnsiReset + ` Provide "add", "edit", "copy", or "gen" as the only argument to receive more specific help
` + AnsiBold + "Tip 4:" + back.AnsiReset + " Using \"add\", \"edit\", or \"copy\" without specifying an option (field) will default to \"password\"\n\n") ` + back.AnsiBold + "Tip 4:" + back.AnsiReset + " Using \"add\", \"edit\", or \"copy\" without specifying an option (field) will default to \"password\"\n\n")
os.Exit(0) os.Exit(0)
} }
func HelpAdd() { func HelpAdd() {
fmt.Print(AnsiBold + "\nUsage:" + back.AnsiReset + ` mutn /<entry name> add <option> fmt.Print(back.AnsiBold + "\nUsage:" + back.AnsiReset + ` mutn /<entry name> add <option>
` + AnsiBold + "Options:" + back.AnsiReset + ` ` + back.AnsiBold + "Options:" + back.AnsiReset + `
add: add:
password|-pw|<blank> Add a password entry password|-pw|<blank> Add a password entry
note|-n Add a note entry note|-n Add a note entry
@@ -72,9 +72,9 @@ func HelpAdd() {
} }
func HelpEdit() { func HelpEdit() {
fmt.Print(AnsiBold + "\nUsage:" + back.AnsiReset + ` mutn /<entry name> edit <option> fmt.Print(back.AnsiBold + "\nUsage:" + back.AnsiReset + ` mutn /<entry name> edit <option>
` + AnsiBold + "Options:" + back.AnsiReset + ` ` + back.AnsiBold + "Options:" + back.AnsiReset + `
edit: edit:
password|-pw|<blank> Change the password in an entry password|-pw|<blank> Change the password in an entry
username|-u Change the username in an entry username|-u Change the username in an entry
@@ -86,9 +86,9 @@ func HelpEdit() {
} }
func HelpCopy() { func HelpCopy() {
fmt.Print(AnsiBold + "\nUsage:" + back.AnsiReset + ` mutn /<entry name> copy <option> fmt.Print(back.AnsiBold + "\nUsage:" + back.AnsiReset + ` mutn /<entry name> copy <option>
` + AnsiBold + "Options:" + back.AnsiReset + ` ` + back.AnsiBold + "Options:" + back.AnsiReset + `
copy: copy:
password|-pw|<blank> Copy the password in an entry to your clipboard password|-pw|<blank> Copy the password in an entry to your clipboard
username|-u Copy the username in an entry to your clipboard username|-u Copy the username in an entry to your clipboard
@@ -99,18 +99,18 @@ func HelpCopy() {
} }
func HelpGen() { func HelpGen() {
fmt.Print(AnsiBold + "\nUsage:" + back.AnsiReset + ` mutn /<entry name> gen [option] fmt.Print(back.AnsiBold + "\nUsage:" + back.AnsiReset + ` mutn /<entry name> gen [option]
` + AnsiBold + "Options:" + back.AnsiReset + ` ` + back.AnsiBold + "Options:" + back.AnsiReset + `
gen: gen:
update|-u Generate a password for an existing entry update|-u Generate a password for an existing entry
` + AnsiBold + "Tip:" + back.AnsiReset + " If no options are provided, a new password entry is generated\n\n") ` + back.AnsiBold + "Tip:" + back.AnsiReset + " If no options are provided, a new password entry is generated\n\n")
os.Exit(0) os.Exit(0)
} }
func MITLicense() { func MITLicense() {
fmt.Print(AnsiBold + "\n MIT License" + back.AnsiReset + ` fmt.Print(back.AnsiBold + "\n MIT License" + back.AnsiReset + `
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated person obtaining a copy of this software and associated
+3 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/rwinkhart/go-boilerplate/back" "github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/go-boilerplate/front" "github.com/rwinkhart/go-boilerplate/front"
"github.com/rwinkhart/libmutton/core" "github.com/rwinkhart/libmutton/core"
"github.com/rwinkhart/libmutton/synccycles"
) )
// inputPasswordGen prompts the user for password generation parameters and returns a generated password as a string. // inputPasswordGen prompts the user for password generation parameters and returns a generated password as a string.
@@ -23,7 +24,7 @@ func inputPasswordGen() string {
complexity = 0.2 // (ultra) complex complexity = 0.2 // (ultra) complex
// 2 and 3 indicate complex and ultra complex charsets, respectively // 2 and 3 indicate complex and ultra complex charsets, respectively
} }
return core.StringGen(passLength, complexity, passCharset) return synccycles.StringGen(passLength, complexity, passCharset)
} }
// writeEntryCLI writes an entry to targetLocation and previews it (errors if no data is supplied). // writeEntryCLI writes an entry to targetLocation and previews it (errors if no data is supplied).
@@ -32,7 +33,7 @@ func writeEntryCLI(targetLocation string, unencryptedEntry []string, hideSecrets
// write the entry to the target location // write the entry to the target location
core.WriteEntry(targetLocation, []byte(strings.Join(unencryptedEntry, "\n"))) core.WriteEntry(targetLocation, []byte(strings.Join(unencryptedEntry, "\n")))
// preview the entry // preview the entry
fmt.Println(AnsiBold + "\nEntry Preview:" + back.AnsiReset) fmt.Println(back.AnsiBold + "\nEntry Preview:" + back.AnsiReset)
EntryReader(unencryptedEntry, hideSecrets, true) EntryReader(unencryptedEntry, hideSecrets, true)
} else { } else {
back.PrintError("No data supplied for entry", back.ErrorTargetNotFound, true) back.PrintError("No data supplied for entry", back.ErrorTargetNotFound, true)