mirror of
https://github.com/rwinkhart/libmutton.git
synced 2026-08-28 12:56:31 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f98e0b577 | ||
|
|
8f23589459 | ||
|
|
1dff1f463a | ||
|
|
4ed816cb11 | ||
|
|
451d695649 | ||
|
|
259d9a3309 | ||
|
|
51bb67f315 | ||
|
|
0f90e82514 | ||
|
|
44924737ab | ||
|
|
17223758b1 | ||
|
|
f40699076d | ||
|
|
a19a4aa1d5 | ||
|
|
456d20256c | ||
|
|
da95ede807 |
@@ -12,14 +12,13 @@ libmutton is a library for building simple, SSH-synchronized password managers i
|
||||
See the [developer guide](https://github.com/rwinkhart/libmutton/blob/main/wiki/developers.md).
|
||||
|
||||
# Roadmap
|
||||
#### Release v0.3.0
|
||||
- [ ] Add refresh/re-encrypt functionality
|
||||
#### Release v0.4.0
|
||||
- [ ] Swap to native (cascade) encryption (custom)
|
||||
- [ ] Add refresh/re-encrypt functionality
|
||||
#### Release v0.5.0
|
||||
- [ ] Implement "netpin" (quick-unlock) with new encryption
|
||||
- [ ] Password aging support
|
||||
- [ ] Append UNIX timestamp to entry names
|
||||
#### Release v0.5.0
|
||||
- [ ] Swap to native (cascade) encryption (custom)
|
||||
- [ ] Implement "netpin" (quick-unlock) with new encryption
|
||||
#### Release v1.0.0
|
||||
- [ ] Create packaging scripts (libmuttonserver)
|
||||
- [ ] Stable source PKGBUILD
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
LibmuttonVersion = "0.3.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"
|
||||
LibmuttonVersion = "0.3.1" // 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"
|
||||
|
||||
FSSpace = "\u259d" // ▝ Space/list separator
|
||||
FSPath = "\u259e" // ▞ Path separator
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
package core
|
||||
|
||||
var EntryRoot = Home + "/.local/share/libmutton" // Path to libmutton entry directory
|
||||
var ConfigDir = Home + "/.config/libmutton" // Path to libmutton configuration directory
|
||||
var ConfigPath = ConfigDir + "/libmutton.ini" // Path to libmutton configuration file
|
||||
var (
|
||||
EntryRoot = Home + "/.local/share/libmutton" // Path to libmutton entry directory
|
||||
ConfigDir = Home + "/.config/libmutton" // Path to libmutton configuration directory
|
||||
ConfigPath = ConfigDir + "/libmutton.ini" // Path to libmutton configuration file
|
||||
)
|
||||
|
||||
const (
|
||||
PathSeparator = "/" // Platform-specific path separator
|
||||
|
||||
+5
-3
@@ -7,9 +7,11 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var EntryRoot = Home + "\\AppData\\Local\\libmutton\\entries" // Path to libmutton entry directory
|
||||
var ConfigDir = Home + "\\AppData\\Local\\libmutton\\config" // Path to libmutton configuration directory
|
||||
var ConfigPath = ConfigDir + "\\libmutton.ini" // Path to libmutton configuration file
|
||||
var (
|
||||
EntryRoot = Home + "\\AppData\\Local\\libmutton\\entries" // Path to libmutton entry directory
|
||||
ConfigDir = Home + "\\AppData\\Local\\libmutton\\config" // Path to libmutton configuration directory
|
||||
ConfigPath = ConfigDir + "\\libmutton.ini" // Path to libmutton configuration file
|
||||
)
|
||||
|
||||
const (
|
||||
PathSeparator = "\\" // Platform-specific path separator
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//go:build android && !termux
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.design/x/clipboard"
|
||||
)
|
||||
|
||||
// clipClearProcess clears the clipboard after 30 seconds if the clipboard contents have not changed.
|
||||
// assignedContents can be omitted to clear the clipboard immediately and unconditionally.
|
||||
func clipClearProcess(assignedContents string) {
|
||||
clearClipboard := func() {
|
||||
clipboard.Write(clipboard.FmtText, []byte(""))
|
||||
Exit(0)
|
||||
}
|
||||
|
||||
// if assignedContents is empty, clear the clipboard immediately and unconditionally
|
||||
if assignedContents == "" {
|
||||
clearClipboard()
|
||||
return
|
||||
}
|
||||
|
||||
// wait 30 seconds before checking clipboard contents
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
newContents := clipboard.Read(clipboard.FmtText)
|
||||
|
||||
if assignedContents == strings.TrimRight(string(newContents), "\r\n") {
|
||||
clearClipboard()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//go:build !android || termux
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// clipClearProcess clears the clipboard after 30 seconds if the clipboard contents have not changed.
|
||||
// assignedContents can be omitted to clear the clipboard immediately and unconditionally.
|
||||
func clipClearProcess(assignedContents string) {
|
||||
cmdPaste, cmdClear := getClipCommands()
|
||||
|
||||
clearClipboard := func() {
|
||||
err := cmdClear.Run()
|
||||
if err != nil {
|
||||
PrintError("Failed to clear clipboard", ErrorClipboard, true)
|
||||
}
|
||||
Exit(0)
|
||||
}
|
||||
|
||||
// if assignedContents is empty, clear the clipboard immediately and unconditionally
|
||||
if assignedContents == "" {
|
||||
clearClipboard()
|
||||
return
|
||||
}
|
||||
|
||||
// wait 30 seconds before checking clipboard contents
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
newContents, err := cmdPaste.Output()
|
||||
if err != nil {
|
||||
PrintError("Failed to read clipboard contents", ErrorClipboard, true)
|
||||
}
|
||||
|
||||
if assignedContents == strings.TrimRight(string(newContents), "\r\n") {
|
||||
clearClipboard()
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func GenDeviceIDList(errorOnFail bool) *[]fs.DirEntry {
|
||||
|
||||
// WriteConfig writes the provided key-value pairs under the specified section headers in the libmutton.ini file.
|
||||
// Requires: valuesToWrite (a slice of length 3 arrays each containing a section, a key name, and a value),
|
||||
// prune (a slice similar to valuesToWrite to allow removing the specified keys from an existing config)
|
||||
// prune (a slice similar to valuesToWrite to allow removing the specified keys from an existing config),
|
||||
// append (set to true to append to the existing libmutton.ini file, false to overwrite it).
|
||||
func WriteConfig(valuesToWrite [][3]string, keysToPrune [][2]string, append bool) {
|
||||
var cfg *ini.File
|
||||
|
||||
@@ -69,38 +69,6 @@ func ClipClearArgument() {
|
||||
}
|
||||
}
|
||||
|
||||
// clipClearProcess clears the clipboard after 30 seconds if the clipboard contents have not changed.
|
||||
// assignedContents can be omitted to clear the clipboard immediately and unconditionally.
|
||||
func clipClearProcess(assignedContents string) {
|
||||
cmdPaste, cmdClear := getClipCommands()
|
||||
|
||||
clearClipboard := func() {
|
||||
err := cmdClear.Run()
|
||||
if err != nil {
|
||||
PrintError("Failed to clear clipboard", ErrorClipboard, true)
|
||||
}
|
||||
Exit(0)
|
||||
}
|
||||
|
||||
// if assignedContents is empty, clear the clipboard immediately and unconditionally
|
||||
if assignedContents == "" {
|
||||
clearClipboard()
|
||||
return
|
||||
}
|
||||
|
||||
// wait 30 seconds before checking clipboard contents
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
newContents, err := cmdPaste.Output()
|
||||
if err != nil {
|
||||
PrintError("Failed to read clipboard contents", ErrorClipboard, true)
|
||||
}
|
||||
|
||||
if assignedContents == strings.TrimRight(string(newContents), "\r\n") {
|
||||
clearClipboard()
|
||||
}
|
||||
}
|
||||
|
||||
// GenTOTP generates a TOTP token from a secret (supports standard and Steam TOTP).
|
||||
func GenTOTP(secret string, time time.Time, forSteam bool) string {
|
||||
var totpToken string
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
//go:build android && !termux
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"golang.design/x/clipboard"
|
||||
)
|
||||
|
||||
// TODO Investigate background clipboard clearing and on-app-close clipboard clearing for Android
|
||||
|
||||
// copyString copies a string to the clipboard.
|
||||
func copyString(continuous bool, copySubject string) {
|
||||
clipboard.Write(clipboard.FmtText, []byte(copySubject))
|
||||
|
||||
if !continuous {
|
||||
LaunchClipClearProcess(copySubject)
|
||||
}
|
||||
}
|
||||
+14
-7
@@ -60,13 +60,7 @@ func DirInit(preserveOldConfigDir bool) string {
|
||||
}
|
||||
|
||||
// get old device ID before its potential removal
|
||||
oldDeviceIDList := GenDeviceIDList(false) // errorOnFail is false so that nil is received when the devices directory does not exist
|
||||
var oldDeviceID string
|
||||
if oldDeviceIDList != nil && len(*oldDeviceIDList) > 0 { // ensure not derferencing nil, which occurs when the devices directory does not exist
|
||||
oldDeviceID = (*oldDeviceIDList)[0].Name()
|
||||
} else {
|
||||
oldDeviceID = FSMisc // indicates to server that no device ID is being replaced
|
||||
}
|
||||
oldDeviceID := GetCurrentDeviceID()
|
||||
|
||||
// remove existing config directory (if it exists and not in append mode)
|
||||
if !preserveOldConfigDir {
|
||||
@@ -87,3 +81,16 @@ func DirInit(preserveOldConfigDir bool) string {
|
||||
|
||||
return oldDeviceID
|
||||
}
|
||||
|
||||
// GetOldDeviceID returns the current device ID or
|
||||
// FSMisc if there is no device ID (e.g. first run).
|
||||
func GetCurrentDeviceID() string {
|
||||
deviceIDList := GenDeviceIDList(false) // errorOnFail is false so that nil is received when the devices directory does not exist
|
||||
var deviceID string
|
||||
if deviceIDList != nil && len(*deviceIDList) > 0 { // ensure not derferencing nil, which occurs when the devices directory does not exist
|
||||
deviceID = (*deviceIDList)[0].Name()
|
||||
} else {
|
||||
deviceID = FSMisc // indicates to server that no device ID is being replaced
|
||||
}
|
||||
return deviceID
|
||||
}
|
||||
|
||||
+35
-10
@@ -109,23 +109,48 @@ func ClampTrailingWhitespace(note []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// EntryAddPrecheck ensures the directory meant to contain a new
|
||||
// entry exists and that the target entry location is not already used.
|
||||
// Returns: statusCode (0 = success, 1 = target location already exists, 2 = containing directory is invalid).
|
||||
func EntryAddPrecheck(targetLocation string) uint8 {
|
||||
// ensure target location does not already exist
|
||||
_, isAccessible := TargetIsFile(targetLocation, false, 0)
|
||||
if isAccessible {
|
||||
PrintError("Target location already exists", ErrorTargetExists, false)
|
||||
return 1 // inform interactive clients that the target location already exists
|
||||
}
|
||||
// ensure target containing directory exists and is a directory (not a file)
|
||||
containingDir := targetLocation[:strings.LastIndex(targetLocation, PathSeparator)]
|
||||
isFile, isAccisAccessible := TargetIsFile(containingDir, false, 1)
|
||||
if isFile || !isAccisAccessible {
|
||||
PrintError("\""+containingDir+"\" is not a valid containing directory", ErrorTargetWrongType, false)
|
||||
return 2 // inform interactive clients that the containing directory is invalid
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// StringGen generates a random string of a specified length and complexity.
|
||||
// Requires: complexity (minimum percentage of special characters to be returned in the generated string; only impacts complex strings),
|
||||
// safeForFileName: (if true, the generated string will only contain special characters that are safe for file names; only impacts complex strings).
|
||||
func StringGen(length int, complex bool, complexity float64, safeForFileName bool) string {
|
||||
// Requires: complexity (minimum percentage of special characters to be returned in the generated string; set to 0 to generate a simple string),
|
||||
// complexCharsetLevel (1 = safe for filenames, 2 = safe for most password entries, 3 = safe only for well-made password entries)
|
||||
func StringGen(length int, complexity float64, complexCharsetLevel uint8) string {
|
||||
var actualSpecialChars int // track the number of special characters in the generated string
|
||||
var minSpecialChars int // track the minimum number of special characters to accept
|
||||
var extendedCharset string // additions to character set used for complex strings
|
||||
|
||||
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" // default character set used for all strings
|
||||
const extendedCharsetFiles = "!#$%&'()+,-.;=@[]^_`{}~" // additional special characters for complex strings (safe in file names)
|
||||
const extendedCharsetPassword = "\"*:><?/\\|" // additional special characters for complex strings (NOT safe in file names)
|
||||
if complex {
|
||||
const extendedCharsetFiles = "!#$%&+,-.;=@_~^()[]{}`'" // additional special characters for complex strings (safe in file names)
|
||||
const extendedCharsetMostPassword = "*:><?|" // additional special characters for complex strings (NOT safe in file names)
|
||||
const extendedCharsetSpecialPassword = "\"/\\" // additional special characters for complex strings (NOT safe in file names)
|
||||
|
||||
if complexity > 0 {
|
||||
minSpecialChars = int(math.Round(float64(length) * complexity)) // determine minimum number of special characters to accept
|
||||
if !safeForFileName {
|
||||
extendedCharset = extendedCharsetFiles + extendedCharsetPassword
|
||||
} else {
|
||||
switch complexCharsetLevel {
|
||||
case 1:
|
||||
extendedCharset = extendedCharsetFiles
|
||||
case 2:
|
||||
extendedCharset = extendedCharsetMostPassword + extendedCharsetFiles[:len(extendedCharsetFiles)-9]
|
||||
case 3:
|
||||
extendedCharset = extendedCharsetFiles + extendedCharsetMostPassword + extendedCharsetSpecialPassword
|
||||
}
|
||||
charset += extendedCharset
|
||||
}
|
||||
@@ -140,7 +165,7 @@ func StringGen(length int, complex bool, complexity float64, safeForFileName boo
|
||||
}
|
||||
|
||||
// return early if the string is not complex
|
||||
if !complex {
|
||||
if complexity == 0 {
|
||||
return string(result)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
**libmutton v0.3.1**
|
||||
April 21, 2025
|
||||
|
||||
## Features
|
||||
- (da95ede80704e667f75372e2142de4cef1dd42da) Added `EntryAddPrecheck()` utility function for ensuring the target locations for new entries are valid
|
||||
- (17223758b11eae301106f398fe2a8815a04f2d62) (44924737ab0830ad49e90c1d1930b05bba307284) (0f90e8251413af2be73f56d071c14af6c00cdb79) Added initial native Android support
|
||||
- (51bb67f3150e70c2c8e8dc3cc72de5666d7c058a) Split device ID retrieval into dedicated exported function, `GetCurrentDeviceID`
|
||||
- (4ed816cb112686d995b1cb7ae8f5df990117ae4d) Allow more control over string generation to improve generated password compatibility
|
||||
|
||||
## Fixes
|
||||
- (51bb67f3150e70c2c8e8dc3cc72de5666d7c058a) Fixed clients having multiple device IDs after failed registrations
|
||||
- (451d695649c659a510a117254a9311fe621082b1) Fixed inability to add a folder on the server if the folder was already created on a client that failed to sync
|
||||
|
||||
## Dependencies
|
||||
- Bumps (direct and indirect)
|
||||
- Go: v1.24.0 => v1.24.2
|
||||
- github.com/pkg/sftp: v1.13.7 => v1.13.9
|
||||
- golang.org/x/crypto: v0.34.0 => v0.37.0
|
||||
- golang.org/x/sys: v0.30.0 => v0.32.0
|
||||
- New
|
||||
- Android builds only
|
||||
- golang.design/x/clipboard v0.7.0
|
||||
- golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
|
||||
- golang.org/x/image v0.26.0
|
||||
- golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7
|
||||
|
||||
---
|
||||
|
||||
**libmutton v0.3.0**
|
||||
February 22, 2025
|
||||
|
||||
## Features
|
||||
- (f9aa2fc374b77fc8325d0c7644ffc9c96169c3c6) (d2034b458b702b8f1664bc188c1840040ec0f704) (81b78068a75a23c73be607cbe6d4dc8b1539f831) `core.LaunchClipClearProcess`, `core.WriteToStdin`, `core.ExpandPathWithHome`, and `core.PrintError` are now exported utility functions for direct use by clients
|
||||
- (f9aa2fc374b77fc8325d0c7644ffc9c96169c3c6) (d2034b458b702b8f1664bc188c1840040ec0f704) (81b78068a75a23c73be607cbe6d4dc8b1539f831) (e9d0c3704362c8963e63a6c3b70d036f3e6c2493) `core.LaunchClipClearProcess`, `core.WriteToStdin`, `core.ExpandPathWithHome`, and `core.PrintError` are now exported utility functions for direct use by clients
|
||||
- (c28d45c9da94ec45d89dbda6d645042a7fdbcd29) One-off sync functions can now be forced to run in offline mode
|
||||
- (5028fb21b019e8a867d031463f5de1402fc053f4) SSH connection attempts now have a 3-second timeout
|
||||
- (89b74cec1e014dba31e2b728e3544941d6edd2c2) Individual keys can now be removed from the config file
|
||||
@@ -12,7 +40,6 @@ February 22, 2025
|
||||
- (2feeeb74d7813dbbb75795d5ccdd817bbcea3602) `core.ParseConfig` now returns errors for proper handling in interactive clients
|
||||
- (68e3108741fae0aaf316dcf33a5074b0e19a7b4f) `sync.RunJob` can now return lists of synchronized entries for display in interactive clients
|
||||
|
||||
|
||||
## Fixes
|
||||
- (eb2b349697ede136ea031cf7d82bfb72a5a0dcf9) Deletions are now synchronized before folders to avoid sync failures under unlikely conditions
|
||||
- (fc1cd349b8c5468afa3feaf7b5b9042eb4c467e7) Double-space line breaks with Markdown formatting are now preserved when saving an entry
|
||||
@@ -26,7 +53,7 @@ February 22, 2025
|
||||
## Optimizations
|
||||
- (f2f9c523f441b49d682795c01454167d68aae573) An unnecessary variable declaration was removed in `core.DecryptGPG`
|
||||
- (fb5449cf1f75aa0f93c0484d590abca04f977d7a) A redundant (and late) check for the pre-existence of a new entry has been removed
|
||||
- (e9d0c3704362c8963e63a6c3b70d036f3e6c2493) `core.PrintError` has been used to decrease the overall binary size through improved code re-use
|
||||
- (e9d0c3704362c8963e63a6c3b70d036f3e6c2493) `core.PrintError` can be used to decrease the overall size of client binaries through code re-use
|
||||
|
||||
## Dependencies
|
||||
- Bumps (direct and indirect)
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
module github.com/rwinkhart/libmutton
|
||||
|
||||
go 1.24.0
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727
|
||||
github.com/pkg/sftp v1.13.7
|
||||
github.com/pkg/sftp v1.13.9
|
||||
github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481
|
||||
golang.org/x/crypto v0.34.0
|
||||
golang.design/x/clipboard v0.7.0 // only for Android builds
|
||||
golang.org/x/crypto v0.37.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/boombuler/barcode v1.0.2 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect; only for Android builds
|
||||
golang.org/x/image v0.26.0 // indirect; only for Android builds
|
||||
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect; only for Android builds
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
)
|
||||
|
||||
@@ -6,10 +6,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727 h1:1RkPJqfzrncAuh9xgoslr9OplZskm+VRA9lkucHPQZ4=
|
||||
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727/go.mod h1:wRAWHbTlpt0C4kwnKoa42L2Phrv6uIq+c50P2uKpb7I=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
|
||||
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
|
||||
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
|
||||
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481 h1:FkxbO331O7mS5EJkP+MCi0o2gswh/Aezs+//NmefrR8=
|
||||
@@ -21,21 +22,42 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo=
|
||||
golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
|
||||
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 h1:8MGTx39304caZ/OMsjPfuxUoDGI2tRas92F5x97tIYc=
|
||||
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7/go.mod h1:ftACcHgQ7vaOnQbHOHvXt9Y6bEPHrs5Ovk67ClwrPJA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -43,26 +65,38 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ package sync
|
||||
|
||||
import "github.com/rwinkhart/libmutton/core"
|
||||
|
||||
var rootLength = len(core.EntryRoot) // length of core.EntryRoot string
|
||||
var RootLength = len(core.EntryRoot) // length of core.EntryRoot string
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -100,7 +101,7 @@ func AddFolderLocal(targetLocationIncomplete string) {
|
||||
err := os.Mkdir(targetLocationComplete, 0700)
|
||||
if err != nil {
|
||||
if os.IsExist(err) {
|
||||
core.PrintError("Directory already exists", core.ErrorTargetExists, true)
|
||||
fmt.Println(ansiUpload + "Directory already exists - libmutton will still ensure it exists on the server")
|
||||
} else {
|
||||
core.PrintError("Failed to create directory: "+err.Error(), core.ErrorWrite, true)
|
||||
}
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ func WalkEntryDir() ([]string, []string) {
|
||||
}
|
||||
|
||||
// trim root path from each path before storing
|
||||
trimmedPath := fullPath[rootLength:]
|
||||
trimmedPath := fullPath[RootLength:]
|
||||
|
||||
// append the path to the appropriate slice
|
||||
if !entry.IsDir() {
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ func WalkEntryDir() ([]string, []string) {
|
||||
}
|
||||
|
||||
// trim root path from each path before storing and replace backslashes with forward slashes
|
||||
trimmedPath := strings.ReplaceAll(fullPath[rootLength:], "\\", "/")
|
||||
trimmedPath := strings.ReplaceAll(fullPath[RootLength:], "\\", "/")
|
||||
|
||||
// append the path to the appropriate slice
|
||||
if !entry.IsDir() {
|
||||
|
||||
+7
-7
@@ -17,7 +17,7 @@ import (
|
||||
func DeviceIDGen(oldDeviceID string) (string, string) {
|
||||
// generate new device ID
|
||||
deviceIDPrefix, _ := os.Hostname()
|
||||
deviceIDSuffix := core.StringGen(rand.Intn(32)+48, true, 0.2, true) + "-" + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
deviceIDSuffix := core.StringGen(rand.Intn(32)+48, 0.2, 1) + "-" + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
newDeviceID := deviceIDPrefix + "-" + deviceIDSuffix
|
||||
|
||||
// create new device ID file (locally)
|
||||
@@ -27,6 +27,12 @@ func DeviceIDGen(oldDeviceID string) (string, string) {
|
||||
}
|
||||
_ = fileToClose.Close() // error ignored; if the file could be created, it can probably be closed
|
||||
|
||||
// remove old device ID file (locally; may not exist)
|
||||
err = os.RemoveAll(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + oldDeviceID)
|
||||
if err != nil {
|
||||
core.PrintError("Failed to remove old device ID file (locally): "+err.Error(), core.ErrorWrite, true)
|
||||
}
|
||||
|
||||
// register new device ID with server and fetch remote EntryRoot and OS type
|
||||
// also removes the old device ID file (remotely)
|
||||
// manualSync is true so the user is alerted if device ID registration fails
|
||||
@@ -37,11 +43,5 @@ func DeviceIDGen(oldDeviceID string) (string, string) {
|
||||
core.PrintError("Init failed - Unable to close SSH client: "+err.Error(), core.ErrorServerConnection, true)
|
||||
}
|
||||
|
||||
// remove old device ID file (locally; may not exist)
|
||||
err = os.RemoveAll(core.ConfigDir + core.PathSeparator + "devices" + core.PathSeparator + oldDeviceID)
|
||||
if err != nil {
|
||||
core.PrintError("Failed to remove old device ID file (locally): "+err.Error(), core.ErrorWrite, true)
|
||||
}
|
||||
|
||||
return sshEntryRootSSHIsWindows[0], sshEntryRootSSHIsWindows[1]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user