14 Commits
Author SHA1 Message Date
RandyTheSilly 8f98e0b577 Use correct string gen mode for generating device ID suffix 2025-04-21 17:59:53 -04:00
RandyTheSilly 8f23589459 Prepare for release v0.3.1 2025-04-21 12:17:38 -04:00
RandyTheSilly 1dff1f463a Update release notes archive to reflect post-release edits 2025-04-20 01:22:34 -04:00
RandyTheSilly 4ed816cb11 Allow more control over string generation to improve generated password compatibility 2025-04-20 01:18:46 -04:00
RandyTheSilly 451d695649 Fix inability to add a folder on the server if the folder was already created on a client that failed to sync 2025-04-20 00:50:27 -04:00
RandyTheSilly 259d9a3309 Bump Go to v1.24.2; bump dependencies 2025-04-12 17:16:32 -04:00
RandyTheSilly 51bb67f315 Break current device ID retrieval out into exported function GetCurrentDeviceID(); fix duplicate device IDs being generated by not deleting the old one before a potentially failed registration 2025-03-19 23:35:50 -04:00
RandyTheSilly 0f90e82514 Add Android (native) clipboard support via gomobile 2025-03-18 22:59:10 -04:00
RandyTheSilly 44924737ab Export sync.RootLength so clients may modify it at runtime 2025-03-18 21:44:14 -04:00
RandyTheSilly 17223758b1 Allow compiling for Android (native, non-Termux); small formatting fixes 2025-03-18 21:05:22 -04:00
RandyTheSilly f40699076d Update version constant to indicate in-development version 2025-03-16 18:25:28 -04:00
RandyTheSilly a19a4aa1d5 Return a status code from EntryAddPrecheck 2025-03-16 18:12:06 -04:00
RandyTheSilly 456d20256c Bump Go to v1.24.1, bump dependencies 2025-03-16 17:34:34 -04:00
RandyTheSilly da95ede807 Add utility function for performing basic path validation for adding new entries 2025-03-16 17:31:55 -04:00
19 changed files with 252 additions and 91 deletions
+4 -5
View File
@@ -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
View File
@@ -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
+5 -3
View File
@@ -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
View File
@@ -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
+34
View File
@@ -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()
}
}
+40
View File
@@ -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()
}
}
+1 -1
View File
@@ -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
-32
View 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
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
+30 -3
View File
@@ -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)
+8 -4
View File
@@ -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
)
+45 -11
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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]
}