Refactor: Split into more packages to reduce server binary size

This commit is contained in:
2025-05-12 16:26:03 +00:00
parent 84d227b803
commit 36bbf88690
28 changed files with 242 additions and 212 deletions
-22
View File
@@ -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
)
-16
View File
@@ -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
)
-16
View File
@@ -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
)
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
-8
View File
@@ -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
}
-10
View File
@@ -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)
}
+7 -5
View File
@@ -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
}