mirror of
https://github.com/rwinkhart/MUTN.git
synced 2026-09-05 16:47:20 -04:00
Sync with latest libmutton development version; draft documentation for release v0.4.0
This commit is contained in:
@@ -23,16 +23,7 @@ Additionally, MUTN is available as a source package ("[mutn](https://aur.archlin
|
|||||||
After installing, please review the [usage guide](https://github.com/rwinkhart/MUTN/blob/main/wiki/usage.md).
|
After installing, please review the [usage guide](https://github.com/rwinkhart/MUTN/blob/main/wiki/usage.md).
|
||||||
|
|
||||||
# Roadmap
|
# Roadmap
|
||||||
### Release v0.4.0
|
|
||||||
- Add interactive copy menu
|
|
||||||
- Send user to this menu after add/gen
|
|
||||||
- libmutton v0.5.0
|
|
||||||
- Password aging support
|
|
||||||
- Add green/yellow/red dot indicators to entry list readout for when passwords should be changed
|
|
||||||
### Release v0.5.0
|
### Release v0.5.0
|
||||||
- libmutton v0.6.0
|
|
||||||
- Implement "netpin" (quick-unlock)
|
|
||||||
### Release v0.6.0
|
|
||||||
- Switch to fully compliant Markdown (do not preserve new lines)
|
- Switch to fully compliant Markdown (do not preserve new lines)
|
||||||
- Switch to alternative Markdown renderer (minimark or BEAN)
|
- Switch to alternative Markdown renderer (minimark or BEAN)
|
||||||
### Release v1.0.0 - Distribution packages
|
### Release v1.0.0 - Distribution packages
|
||||||
|
|||||||
@@ -4,4 +4,25 @@
|
|||||||
Built with libmutton v0.5.0
|
Built with libmutton v0.5.0
|
||||||
Release date TBD
|
Release date TBD
|
||||||
|
|
||||||
This release syncs with libmutton v0.5.0 and adds a new multi-field copy menu.
|
# The ??? Update
|
||||||
|
This release adds password age-tracking support, a new interactive copy menu, and features many under-the-hood (breaking) changes.
|
||||||
|
|
||||||
|
## libmutton-derived Changes
|
||||||
|
- **PLEASE** read [libmutton's release notes](https://github.com/rwinkhart/libmutton/releases/tag/v0.5.0), as they detail severe breaking changes. You **NEED** to update your `libmuttonserver` and run `mutn init` after this update!
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- (41b4c4b6ca2f5a74899e87c6d520df7b140671b3) (c1c95655114a689daf80d2410e19ba35b670b1b1) (107a6f4f1250de8f94d87c192d58cc47bef80c1f) (fc1e3b11fc810a79cc0673b6edf96d4753c0300c) (a75416a6792559179e39497cb033407f18c00620) (b58461b9c458795278f050ff10e91a2d05c21745) (b26c2ce17c0491890de03dc131717d557876a3e4) (8112496006c7d48dd4a8f21a9eea51eadc30f732) (12167b1eeddb49e8f5e62f788dc1da6265706f68) (aaf69eb590af4dc37013102dcff79ef115b9f809) (cac35e1d25b7a9a4bd7263406f642834418f6b76) Add new interactive copy menu
|
||||||
|
- This menu aims to reduce the number of times the user is decrypting each entry
|
||||||
|
- (1a9de90f890f133e6ab8f298241fe3a6237ba4c8) (7016a8a1309b06c63b9dd63fb126103d4cab6ccc) Password age tracking (from libmutton)
|
||||||
|
- The `tweak` menu now allows adding age data to old (existing) entries
|
||||||
|
|
||||||
|
## Dependencies (direct/replaced)
|
||||||
|
- Dropped (libmutton)
|
||||||
|
- golang.design/x/clipboard
|
||||||
|
- gopkg.in/ini.v1
|
||||||
|
- Bumped
|
||||||
|
- Go: v1.25.4 => 1.25.5
|
||||||
|
- github.com/rwinkhart/go-boilerplate: v0.1.0 => v0.1.1
|
||||||
|
- github.com/rwinkhart/libmutton: v0.4.4 => v0.5.0
|
||||||
|
- golang.org/x/term: v0.37.0 => v0.38.0
|
||||||
|
- golang.org/x/sys: v0.38.0 => v0.40.0
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ go 1.25.5
|
|||||||
require (
|
require (
|
||||||
github.com/charmbracelet/glamour v0.7.0
|
github.com/charmbracelet/glamour v0.7.0
|
||||||
github.com/rwinkhart/go-boilerplate v0.1.1
|
github.com/rwinkhart/go-boilerplate v0.1.1
|
||||||
github.com/rwinkhart/libmutton v0.4.3-0.20260108232004-d54b59030e72
|
github.com/rwinkhart/libmutton v0.4.3-0.20260109022300-a54d15e33111
|
||||||
golang.org/x/term v0.38.0
|
golang.org/x/term v0.38.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ github.com/rwinkhart/go-highlite v0.1.1 h1:9TxbRhYVfD/3YaEgNk1BtEiZ0t8/5mxDUZqNM
|
|||||||
github.com/rwinkhart/go-highlite v0.1.1/go.mod h1:mWLMtCWcyV0BG4NeyPyAUGMjPvI0ds6vXmUq89QwBFA=
|
github.com/rwinkhart/go-highlite v0.1.1/go.mod h1:mWLMtCWcyV0BG4NeyPyAUGMjPvI0ds6vXmUq89QwBFA=
|
||||||
github.com/rwinkhart/go-winio v0.1.0 h1:b72agLW+dETGmhR3VbcbwnStfgKfc5AfgJOXBJDkaHg=
|
github.com/rwinkhart/go-winio v0.1.0 h1:b72agLW+dETGmhR3VbcbwnStfgKfc5AfgJOXBJDkaHg=
|
||||||
github.com/rwinkhart/go-winio v0.1.0/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0=
|
github.com/rwinkhart/go-winio v0.1.0/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0=
|
||||||
github.com/rwinkhart/libmutton v0.4.3-0.20260108232004-d54b59030e72 h1:sFtwqO//25lLwsD77wtxyojRKSrK+ABVZSWIZg5mf1w=
|
github.com/rwinkhart/libmutton v0.4.3-0.20260109022300-a54d15e33111 h1:nxQHhtdRVIUhRAc/5oYTli30tive65c8N7u75dENVWE=
|
||||||
github.com/rwinkhart/libmutton v0.4.3-0.20260108232004-d54b59030e72/go.mod h1:qInUv19lzX1adFNI6Ih/IEVy+pktSTh/yA7cRTdVPH8=
|
github.com/rwinkhart/libmutton v0.4.3-0.20260109022300-a54d15e33111/go.mod h1:qInUv19lzX1adFNI6Ih/IEVy+pktSTh/yA7cRTdVPH8=
|
||||||
github.com/rwinkhart/peercred-mini v0.1.2 h1:4cGWDbv0whvLeVvbUdx84V/9p+2fS+DEXgrA1KxlRFo=
|
github.com/rwinkhart/peercred-mini v0.1.2 h1:4cGWDbv0whvLeVvbUdx84V/9p+2fS+DEXgrA1KxlRFo=
|
||||||
github.com/rwinkhart/peercred-mini v0.1.2/go.mod h1:LLHG7YshHEpbpJJP+Il9nx2dnGj5O3VGE32rWmflj0c=
|
github.com/rwinkhart/peercred-mini v0.1.2/go.mod h1:LLHG7YshHEpbpJJP+Il9nx2dnGj5O3VGE32rWmflj0c=
|
||||||
github.com/rwinkhart/rcw v0.2.4 h1:FrRSsl+ui1WLmDwqjLSROtFPKqSeGLEfF2SPENt4qP0=
|
github.com/rwinkhart/rcw v0.2.4 h1:FrRSsl+ui1WLmDwqjLSROtFPKqSeGLEfF2SPENt4qP0=
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ func main() {
|
|||||||
case "show", "-s":
|
case "show", "-s":
|
||||||
cli.EntryReaderDecrypt(realPath, false)
|
cli.EntryReaderDecrypt(realPath, false)
|
||||||
case "copy":
|
case "copy":
|
||||||
err := clip.CopyShortcut(realPath, 0)
|
if err := clip.CopyShortcut(realPath, 0); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to copy password to clipboard: "+err.Error(), global.ErrorClipboard)
|
other.PrintError("Failed to copy password to clipboard: "+err.Error(), global.ErrorClipboard)
|
||||||
}
|
}
|
||||||
case "edit":
|
case "edit":
|
||||||
@@ -59,8 +58,7 @@ func main() {
|
|||||||
case "add":
|
case "add":
|
||||||
cli.AddEntry(realPath, 0)
|
cli.AddEntry(realPath, 0)
|
||||||
case "shear":
|
case "shear":
|
||||||
err := syncclient.ShearRemote(args[1], false) // pass the incomplete path as the server and all clients (reading from the deletions directory) will have a different home directory
|
if err := syncclient.ShearRemote(args[1], false); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to shear target: "+err.Error(), back.ErrorWrite)
|
other.PrintError("Failed to shear target: "+err.Error(), back.ErrorWrite)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -100,8 +98,7 @@ func main() {
|
|||||||
default:
|
default:
|
||||||
cli.HelpCopy()
|
cli.HelpCopy()
|
||||||
}
|
}
|
||||||
err := clip.CopyShortcut(realPath, field)
|
if err := clip.CopyShortcut(realPath, field); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to copy field to clipboard: "+err.Error(), global.ErrorClipboard)
|
other.PrintError("Failed to copy field to clipboard: "+err.Error(), global.ErrorClipboard)
|
||||||
}
|
}
|
||||||
case "edit":
|
case "edit":
|
||||||
@@ -169,8 +166,7 @@ func main() {
|
|||||||
case "note", "-n":
|
case "note", "-n":
|
||||||
cli.AddEntry(realPath, 2)
|
cli.AddEntry(realPath, 2)
|
||||||
case "folder", "-f":
|
case "folder", "-f":
|
||||||
err := syncclient.AddFolderRemote(args[1]) // pass the incomplete path as the server will have a different home directory
|
if err := syncclient.AddFolderRemote(args[1]); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to add folder: "+err.Error(), back.ErrorWrite)
|
other.PrintError("Failed to add folder: "+err.Error(), back.ErrorWrite)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -185,8 +181,7 @@ func main() {
|
|||||||
} else {
|
} else {
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "clipclear":
|
case "clipclear":
|
||||||
err := clip.ClearArgument()
|
if err := clip.ClearArgument(); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failure occurred in clipboard clearing process: "+err.Error(), global.ErrorClipboard)
|
other.PrintError("Failure occurred in clipboard clearing process: "+err.Error(), global.ErrorClipboard)
|
||||||
}
|
}
|
||||||
case "startrcwd":
|
case "startrcwd":
|
||||||
@@ -215,8 +210,7 @@ func main() {
|
|||||||
newCfg := &config.CfgT{}
|
newCfg := &config.CfgT{}
|
||||||
newClientSpecificCfg := map[string]any{"mutnTextEditor": getTextEditorInput()}
|
newClientSpecificCfg := map[string]any{"mutnTextEditor": getTextEditorInput()}
|
||||||
newCfg.ClientSpecific = &newClientSpecificCfg
|
newCfg.ClientSpecific = &newClientSpecificCfg
|
||||||
err := config.Write(newCfg, true)
|
if err := config.Write(newCfg, true); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to set text editor: "+err.Error(), back.ErrorWrite)
|
other.PrintError("Failed to set text editor: "+err.Error(), back.ErrorWrite)
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
@@ -224,7 +218,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
other.PrintError("Failed to get current device ID: "+err.Error(), back.ErrorRead)
|
other.PrintError("Failed to get current device ID: "+err.Error(), back.ErrorRead)
|
||||||
}
|
}
|
||||||
_, _, err = syncclient.GenDeviceID(oldDeviceID, "")
|
_, _, _, err = syncclient.GenDeviceID(oldDeviceID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
other.PrintError("Failed to change device ID: "+err.Error(), global.ErrorSyncProcess)
|
other.PrintError("Failed to change device ID: "+err.Error(), global.ErrorSyncProcess)
|
||||||
}
|
}
|
||||||
@@ -232,16 +226,14 @@ func main() {
|
|||||||
case 3:
|
case 3:
|
||||||
forceReage := front.InputBinary("Re-age aged entries?")
|
forceReage := front.InputBinary("Re-age aged entries?")
|
||||||
fmt.Println("Aging entries; this may take awhile - do not terminate this process")
|
fmt.Println("Aging entries; this may take awhile - do not terminate this process")
|
||||||
err := age.AllPasswordEntries(forceReage)
|
if err := age.AllPasswordEntries(forceReage); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to age entries: "+err.Error(), 1)
|
other.PrintError("Failed to age entries: "+err.Error(), 1)
|
||||||
}
|
}
|
||||||
case 4:
|
case 4:
|
||||||
oldPassword := confirmRCWPassword("old")
|
oldPassword := confirmRCWPassword("old")
|
||||||
newPassword := confirmRCWPassword("new")
|
newPassword := confirmRCWPassword("new")
|
||||||
fmt.Print("\nRe-encrypting entries. Please wait; do not force close this process.\n")
|
fmt.Print("\nRe-encrypting entries. Please wait; do not force close this process.\n")
|
||||||
err := core.EntryRefresh(oldPassword, newPassword, false)
|
if err := core.EntryRefresh(oldPassword, newPassword, false); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Re-encryption failed: "+err.Error(), global.ErrorEncryption)
|
other.PrintError("Re-encryption failed: "+err.Error(), global.ErrorEncryption)
|
||||||
}
|
}
|
||||||
fmt.Println("\nRe-encryption complete.")
|
fmt.Println("\nRe-encryption complete.")
|
||||||
|
|||||||
+2
-4
@@ -102,8 +102,7 @@ func CopyMenu(vanityPath string, decSlice []string, oldPassword string) {
|
|||||||
}
|
}
|
||||||
selectedField = fieldOptions[choice-1]
|
selectedField = fieldOptions[choice-1]
|
||||||
if selectedField == "Old Password" {
|
if selectedField == "Old Password" {
|
||||||
err = clip.CopyString(false, oldPassword)
|
if err = clip.CopyString(false, oldPassword); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to copy old password to clipboard: "+err.Error(), global.ErrorClipboard)
|
other.PrintError("Failed to copy old password to clipboard: "+err.Error(), global.ErrorClipboard)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -115,8 +114,7 @@ func CopyMenu(vanityPath string, decSlice []string, oldPassword string) {
|
|||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
go clip.TOTPCopier(decSlice[2], errorChan, done)
|
go clip.TOTPCopier(decSlice[2], errorChan, done)
|
||||||
// block until first successful copy
|
// block until first successful copy
|
||||||
err = <-errorChan
|
if err = <-errorChan; err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to copy TOTP token: "+err.Error(), global.ErrorClipboard)
|
other.PrintError("Failed to copy TOTP token: "+err.Error(), global.ErrorClipboard)
|
||||||
}
|
}
|
||||||
fmt.Println(back.AnsiGreen + "[Started]" + back.AnsiReset + " TOTP clipboard refresher")
|
fmt.Println(back.AnsiGreen + "[Started]" + back.AnsiReset + " TOTP clipboard refresher")
|
||||||
|
|||||||
+2
-4
@@ -18,8 +18,7 @@ import (
|
|||||||
func RenameCli(oldVanityPath string) {
|
func RenameCli(oldVanityPath string) {
|
||||||
// prompt user for new location and rename
|
// prompt user for new location and rename
|
||||||
newVanityPath := front.Input("New location:")
|
newVanityPath := front.Input("New location:")
|
||||||
err := syncclient.RenameRemote(oldVanityPath, newVanityPath)
|
if err := syncclient.RenameRemote(oldVanityPath, newVanityPath); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to rename entry: "+err.Error(), back.ErrorWrite)
|
other.PrintError("Failed to rename entry: "+err.Error(), back.ErrorWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,8 +130,7 @@ func editNote(baseNote []string) ([]string, bool) {
|
|||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
err = cmd.Run()
|
if err = cmd.Run(); err != nil {
|
||||||
if err != nil {
|
|
||||||
panic(back.AnsiError + "Failed to write note with " + editor + back.AnsiReset) // panic is used to ensure the tempFile is removed, as per the defer statement
|
panic(back.AnsiError + "Failed to write note with " + editor + back.AnsiReset) // panic is used to ensure the tempFile is removed, as per the defer statement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ func inputPasswordGen() string {
|
|||||||
// writeEntryCLI writes an entry to realPath and previews it (errors if no data is supplied).
|
// writeEntryCLI writes an entry to realPath and previews it (errors if no data is supplied).
|
||||||
func writeEntryCLI(realPath string, decSlice []string, passwordIsNew bool, oldPassword string) {
|
func writeEntryCLI(realPath string, decSlice []string, passwordIsNew bool, oldPassword string) {
|
||||||
if core.EntryIsNotEmpty(decSlice) {
|
if core.EntryIsNotEmpty(decSlice) {
|
||||||
err := core.WriteEntry(realPath, decSlice, passwordIsNew)
|
if err := core.WriteEntry(realPath, decSlice, passwordIsNew); err != nil {
|
||||||
if err != nil {
|
|
||||||
other.PrintError("Failed to write entry: "+err.Error(), back.ErrorWrite)
|
other.PrintError("Failed to write entry: "+err.Error(), back.ErrorWrite)
|
||||||
}
|
}
|
||||||
// preview the entry
|
// preview the entry
|
||||||
|
|||||||
Reference in New Issue
Block a user