mirror of
https://github.com/rwinkhart/libmutton.git
synced 2026-08-30 13:56:40 -04:00
Refactor: Split into more packages to reduce server binary size
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
package core
|
||||
|
||||
type ByteInputFetcher func(prompt string) []byte
|
||||
|
||||
var (
|
||||
GetPassphrase ByteInputFetcher // Clients should set this to a function that fetches hidden input from the user
|
||||
)
|
||||
|
||||
const (
|
||||
LibmuttonVersion = "0.E.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"
|
||||
|
||||
FSSpace = "\u259d" // ▝ Space/list separator
|
||||
FSPath = "\u259e" // ▞ Path separator
|
||||
FSMisc = "\u259f" // ▟ Misc. field separator (if \u259d is already used)
|
||||
|
||||
ErrorSyncProcess = 103
|
||||
ErrorServerConnection = 104
|
||||
ErrorTargetExists = 106
|
||||
ErrorDecryption = 108
|
||||
ErrorEncryption = 109
|
||||
ErrorClipboard = 110
|
||||
)
|
||||
@@ -1,16 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package core
|
||||
|
||||
import "github.com/rwinkhart/go-boilerplate/back"
|
||||
|
||||
var (
|
||||
EntryRoot = back.Home + "/.local/share/libmutton" // Path to libmutton entry directory
|
||||
ConfigDir = back.Home + "/.config/libmutton" // Path to libmutton configuration directory
|
||||
ConfigPath = ConfigDir + "/libmutton.ini" // Path to libmutton configuration file
|
||||
)
|
||||
|
||||
const (
|
||||
PathSeparator = "/" // Platform-specific path separator
|
||||
IsWindows = false // Platform indicator
|
||||
)
|
||||
@@ -1,16 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package core
|
||||
|
||||
import "github.com/rwinkhart/go-boilerplate/back"
|
||||
|
||||
var (
|
||||
EntryRoot = back.Home + "\\AppData\\Local\\libmutton\\entries" // Path to libmutton entry directory
|
||||
ConfigDir = back.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
|
||||
IsWindows = true // Platform indicator
|
||||
)
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
)
|
||||
|
||||
// clipClearProcess clears the clipboard after 30 seconds if the clipboard contents have not changed.
|
||||
@@ -17,7 +18,7 @@ func clipClearProcess(assignedContents string) {
|
||||
clearClipboard := func() {
|
||||
err := cmdClear.Run()
|
||||
if err != nil {
|
||||
back.PrintError("Failed to clear clipboard", ErrorClipboard, true)
|
||||
back.PrintError("Failed to clear clipboard", global.ErrorClipboard, true)
|
||||
}
|
||||
back.Exit(0)
|
||||
}
|
||||
@@ -33,7 +34,7 @@ func clipClearProcess(assignedContents string) {
|
||||
|
||||
newContents, err := cmdPaste.Output()
|
||||
if err != nil {
|
||||
back.PrintError("Failed to read clipboard contents", ErrorClipboard, true)
|
||||
back.PrintError("Failed to read clipboard contents", global.ErrorClipboard, true)
|
||||
}
|
||||
|
||||
if assignedContents == strings.TrimRight(string(newContents), "\r\n") {
|
||||
|
||||
+3
-19
@@ -2,17 +2,16 @@ package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// loadConfig loads the libmutton.ini file and returns the configuration.
|
||||
// It is a utility function for ParseConfig and WriteConfig; do not call directly.
|
||||
func loadConfig() *ini.File {
|
||||
cfg, err := ini.Load(ConfigPath)
|
||||
cfg, err := ini.Load(global.ConfigPath)
|
||||
if err != nil {
|
||||
back.PrintError("Failed to load libmutton.ini: "+err.Error(), back.ErrorRead, true)
|
||||
}
|
||||
@@ -54,21 +53,6 @@ func ParseConfig(valuesRequested [][2]string, missingValueError string) ([]strin
|
||||
return config, err
|
||||
}
|
||||
|
||||
// GenDeviceIDList returns a pointer to a slice of all registered device IDs.
|
||||
// Requires: errorOnFail (set to true to throw an error if the devices directory cannot be read/does not exist)
|
||||
func GenDeviceIDList(errorOnFail bool) *[]fs.DirEntry {
|
||||
// create a slice of all registered devices
|
||||
deviceIDList, err := os.ReadDir(ConfigDir + PathSeparator + "devices")
|
||||
if err != nil {
|
||||
if errorOnFail {
|
||||
back.PrintError("Failed to read the devices directory: "+err.Error(), back.ErrorRead, true)
|
||||
} else {
|
||||
return nil // a nil return value indicates that the devices directory could not be read/does not exist
|
||||
}
|
||||
}
|
||||
return &deviceIDList
|
||||
}
|
||||
|
||||
// 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),
|
||||
@@ -108,7 +92,7 @@ func WriteConfig(valuesToWrite [][3]string, keysToPrune [][2]string, append bool
|
||||
}
|
||||
|
||||
// save to libmutton.ini
|
||||
err := cfg.SaveTo(ConfigPath)
|
||||
err := cfg.SaveTo(global.ConfigPath)
|
||||
if err != nil {
|
||||
back.PrintError("Failed to save libmutton.ini: "+err.Error(), back.ErrorWrite, true)
|
||||
}
|
||||
|
||||
+2
-1
@@ -9,13 +9,14 @@ import (
|
||||
steamtotp "github.com/fortis/go-steam-totp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/crypt"
|
||||
)
|
||||
|
||||
// CopyArgument copies a field from an entry to the clipboard.
|
||||
func CopyArgument(targetLocation string, field int) {
|
||||
if isFile, _ := back.TargetIsFile(targetLocation, true, 2); isFile {
|
||||
|
||||
decryptedEntry := DecryptFileToSlice(targetLocation)
|
||||
decryptedEntry := crypt.DecryptFileToSlice(targetLocation)
|
||||
var copySubject string // will store data to be copied
|
||||
|
||||
// ensure field exists in entry
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
)
|
||||
|
||||
// copyString copies a string to the clipboard.
|
||||
@@ -14,7 +15,7 @@ func copyString(continuous bool, copySubject string) {
|
||||
back.WriteToStdin(cmd, copySubject)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
back.PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true)
|
||||
back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true)
|
||||
}
|
||||
|
||||
if !continuous {
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
)
|
||||
|
||||
// copyString copies a string to the clipboard.
|
||||
@@ -14,7 +15,7 @@ func copyString(continuous bool, copySubject string) {
|
||||
back.WriteToStdin(cmd, copySubject)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
back.PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true)
|
||||
back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true)
|
||||
}
|
||||
|
||||
if !continuous {
|
||||
|
||||
+3
-2
@@ -7,6 +7,7 @@ import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
)
|
||||
|
||||
// copyString copies a string to the clipboard.
|
||||
@@ -20,13 +21,13 @@ func copyString(continuous bool, copySubject string) {
|
||||
} else if _, envSet = os.LookupEnv("DISPLAY"); envSet {
|
||||
cmdCopy = exec.Command("xclip", "-sel", "c", "-t", "text/plain")
|
||||
} else {
|
||||
back.PrintError("Clipboard platform could not be determined", ErrorClipboard, true)
|
||||
back.PrintError("Clipboard platform could not be determined", global.ErrorClipboard, true)
|
||||
}
|
||||
|
||||
back.WriteToStdin(cmdCopy, copySubject)
|
||||
err := cmdCopy.Run()
|
||||
if err != nil {
|
||||
back.PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true)
|
||||
back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true)
|
||||
}
|
||||
|
||||
if !continuous {
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
)
|
||||
|
||||
// copyString copies a string to the clipboard.
|
||||
@@ -15,7 +16,7 @@ func copyString(continuous bool, copySubject string) {
|
||||
cmd := exec.Command("powershell.exe", "-c", fmt.Sprintf("echo '%s' | Set-Clipboard", strings.ReplaceAll(copySubject, "'", "''")))
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
back.PrintError("Failed to copy to clipboard: "+err.Error(), ErrorClipboard, true)
|
||||
back.PrintError("Failed to copy to clipboard: "+err.Error(), global.ErrorClipboard, true)
|
||||
}
|
||||
|
||||
if !continuous {
|
||||
|
||||
+5
-2
@@ -1,6 +1,9 @@
|
||||
package core
|
||||
|
||||
import "github.com/rwinkhart/go-boilerplate/back"
|
||||
import (
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/crypt"
|
||||
)
|
||||
|
||||
// GetOldEntryData decrypts and returns old entry data (with all required lines present).
|
||||
func GetOldEntryData(targetLocation string, field int) []string {
|
||||
@@ -8,7 +11,7 @@ func GetOldEntryData(targetLocation string, field int) []string {
|
||||
back.TargetIsFile(targetLocation, true, 2)
|
||||
|
||||
// read old entry data
|
||||
unencryptedEntry := DecryptFileToSlice(targetLocation)
|
||||
unencryptedEntry := crypt.DecryptFileToSlice(targetLocation)
|
||||
|
||||
// return the old entry data with all required lines present
|
||||
if field > 0 {
|
||||
|
||||
+2
-48
@@ -1,61 +1,15 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
"github.com/rwinkhart/rcw/wrappers"
|
||||
)
|
||||
|
||||
// RCWSanityCheckGen generates the RCW sanity check file for libmutton.
|
||||
func RCWSanityCheckGen(passphrase []byte) {
|
||||
err := wrappers.GenSanityCheck(ConfigDir+PathSeparator+"sanity.rcw", passphrase)
|
||||
err := wrappers.GenSanityCheck(global.ConfigDir+global.PathSeparator+"sanity.rcw", passphrase)
|
||||
if err != nil {
|
||||
back.PrintError("Failed to generate sanity check file: "+err.Error(), back.ErrorWrite, true)
|
||||
}
|
||||
}
|
||||
|
||||
// DirInit creates the libmutton directories.
|
||||
// Returns: oldDeviceID (from before the directory reset; will be FSMisc if there is no pre-existing ID).
|
||||
func DirInit(preserveOldConfigDir bool) string {
|
||||
// create EntryRoot
|
||||
err := os.MkdirAll(EntryRoot, 0700)
|
||||
if err != nil {
|
||||
back.PrintError("Failed to create \""+EntryRoot+"\": "+err.Error(), back.ErrorWrite, true)
|
||||
}
|
||||
|
||||
// get old device ID before its potential removal
|
||||
oldDeviceID := GetCurrentDeviceID()
|
||||
|
||||
// remove existing config directory (if it exists and not in append mode)
|
||||
if !preserveOldConfigDir {
|
||||
_, isAccessible := back.TargetIsFile(ConfigDir, false, 1)
|
||||
if isAccessible {
|
||||
err = os.RemoveAll(ConfigDir)
|
||||
if err != nil {
|
||||
back.PrintError("Failed to remove existing config directory: "+err.Error(), back.ErrorWrite, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create config directory w/devices subdirectory
|
||||
err = os.MkdirAll(ConfigDir+PathSeparator+"devices", 0700)
|
||||
if err != nil {
|
||||
back.PrintError("Failed to create \""+ConfigDir+"\": "+err.Error(), back.ErrorWrite, true)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/rcw/daemon"
|
||||
"github.com/rwinkhart/rcw/wrappers"
|
||||
)
|
||||
|
||||
// RCWDArgument reads the passphrase from stdin and caches it via an RCW daemon.
|
||||
func RCWDArgument() {
|
||||
passphrase := back.ReadFromStdin()
|
||||
if passphrase == "" {
|
||||
os.Exit(0)
|
||||
}
|
||||
daemon.Start([]byte(passphrase))
|
||||
}
|
||||
|
||||
// DecryptFileToSlice decrypts an RCW wrapped file and returns the contents as a slice of (trimmed) strings.
|
||||
func DecryptFileToSlice(targetLocation string) []string {
|
||||
// read encrypted file
|
||||
encBytes, err := os.ReadFile(targetLocation)
|
||||
if err != nil {
|
||||
back.PrintError("Failed to open \""+targetLocation+"\" for decryption - "+err.Error(), back.ErrorRead, true)
|
||||
}
|
||||
|
||||
// decrypt data using RCW daemon
|
||||
passphrase := launchRCWDProcess()
|
||||
if passphrase == nil {
|
||||
// if daemon is already running, use it to decrypt the data
|
||||
return strings.Split(string(daemon.GetDec(encBytes)), "\n")
|
||||
}
|
||||
// if the daemon is not already running, use wrappers.Decrypt
|
||||
// directly to avoid waiting for socket file creation
|
||||
decBytes, err := wrappers.Decrypt(encBytes, passphrase)
|
||||
if err != nil {
|
||||
back.PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true)
|
||||
}
|
||||
return strings.Split(string(decBytes), "\n")
|
||||
}
|
||||
|
||||
// EncryptBytes encrypts a byte slice using RCW and returns the encrypted data.
|
||||
func EncryptBytes(decBytes []byte) []byte {
|
||||
passphrase := launchRCWDProcess()
|
||||
if passphrase == nil {
|
||||
// if daemon is already running, use it to encrypt the data
|
||||
return daemon.GetEnc(decBytes)
|
||||
}
|
||||
// if the daemon is not already running, use wrappers.Encrypt
|
||||
// directly to avoid waiting for socket file creation
|
||||
return wrappers.Encrypt(decBytes, passphrase)
|
||||
}
|
||||
|
||||
// launchRCWDProcess launches an RCW daemon to cache a passphrase.
|
||||
// If the daemon is not already running, it returns the passphrase (otherwise returns nil).
|
||||
func launchRCWDProcess() []byte {
|
||||
if daemon.IsOpen() {
|
||||
return nil
|
||||
}
|
||||
var passphrase []byte
|
||||
for {
|
||||
passphrase = GetPassphrase("RCW Passphrase:")
|
||||
err := wrappers.RunSanityCheck(ConfigDir+PathSeparator+"sanity.rcw", passphrase)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
fmt.Println(back.AnsiError + "Incorrect passphrase" + back.AnsiReset)
|
||||
}
|
||||
cmd := exec.Command(os.Args[0], "startrcwd")
|
||||
back.WriteToStdin(cmd, string(passphrase))
|
||||
cmd.Start()
|
||||
|
||||
return passphrase
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package core
|
||||
|
||||
// TargetLocationFormat returns the full location of an entry (given the name) formatted for the current platform.
|
||||
func TargetLocationFormat(targetLocationIncomplete string) string {
|
||||
return EntryRoot + targetLocationIncomplete
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package core
|
||||
|
||||
import "strings"
|
||||
|
||||
// TargetLocationFormat returns the full location of an entry (given the name) formatted for the current platform.
|
||||
func TargetLocationFormat(targetLocationIncomplete string) string {
|
||||
return EntryRoot + strings.ReplaceAll(targetLocationIncomplete, "/", PathSeparator)
|
||||
}
|
||||
@@ -9,11 +9,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/crypt"
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
)
|
||||
|
||||
// WriteEntry writes entryData to an encrypted file at targetLocation.
|
||||
func WriteEntry(targetLocation string, entryData []byte) {
|
||||
encBytes := EncryptBytes(entryData)
|
||||
encBytes := crypt.EncryptBytes(entryData)
|
||||
err := os.WriteFile(targetLocation, encBytes, 0600)
|
||||
if err != nil {
|
||||
back.PrintError("Failed to write to file: "+err.Error(), back.ErrorWrite, true)
|
||||
@@ -57,13 +59,13 @@ func EntryAddPrecheck(targetLocation string) uint8 {
|
||||
// ensure target location does not already exist
|
||||
_, isAccessible := back.TargetIsFile(targetLocation, false, 0)
|
||||
if isAccessible {
|
||||
back.PrintError("Target location already exists", ErrorTargetExists, false)
|
||||
back.PrintError("Target location already exists", global.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 := back.TargetIsFile(containingDir, false, 1)
|
||||
if isFile || !isAccisAccessible {
|
||||
containingDir := targetLocation[:strings.LastIndex(targetLocation, global.PathSeparator)]
|
||||
isFile, isAccessible := back.TargetIsFile(containingDir, false, 1)
|
||||
if isFile || !isAccessible {
|
||||
back.PrintError("\""+containingDir+"\" is not a valid containing directory", back.ErrorTargetWrongType, false)
|
||||
return 2 // inform interactive clients that the containing directory is invalid
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user