added keychain support
This commit is contained in:
3
go.mod
3
go.mod
@@ -5,8 +5,10 @@ go 1.24.3
|
|||||||
require fyne.io/fyne/v2 v2.6.1
|
require fyne.io/fyne/v2 v2.6.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
||||||
fyne.io/systray v1.11.0 // indirect
|
fyne.io/systray v1.11.0 // indirect
|
||||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
|
github.com/danieljoos/wincred v1.2.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fredbi/uri v1.1.0 // indirect
|
github.com/fredbi/uri v1.1.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
@@ -32,6 +34,7 @@ require (
|
|||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||||
github.com/stretchr/testify v1.10.0 // indirect
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
github.com/yuin/goldmark v1.7.8 // indirect
|
||||||
|
github.com/zalando/go-keyring v0.2.6 // indirect
|
||||||
golang.org/x/image v0.24.0 // indirect
|
golang.org/x/image v0.24.0 // indirect
|
||||||
golang.org/x/net v0.35.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
6
go.sum
6
go.sum
@@ -1,3 +1,5 @@
|
|||||||
|
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
|
||||||
|
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
|
||||||
fyne.io/fyne/v2 v2.6.1 h1:kjPJD4/rBS9m2nHJp+npPSuaK79yj6ObMTuzR6VQ1Is=
|
fyne.io/fyne/v2 v2.6.1 h1:kjPJD4/rBS9m2nHJp+npPSuaK79yj6ObMTuzR6VQ1Is=
|
||||||
fyne.io/fyne/v2 v2.6.1/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
|
fyne.io/fyne/v2 v2.6.1/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
|
||||||
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
|
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
|
||||||
@@ -5,6 +7,8 @@ fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
|||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
|
||||||
|
github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||||
@@ -65,6 +69,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
|
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
|
||||||
|
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
|
||||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
|
@@ -100,18 +100,52 @@ func StartRosettaX87Service(myWindow fyne.Window, updateAllStatuses func()) {
|
|||||||
// Clean up any existing rosettax87 processes first
|
// Clean up any existing rosettax87 processes first
|
||||||
CleanupExistingServices()
|
CleanupExistingServices()
|
||||||
|
|
||||||
|
// Load user preferences
|
||||||
|
prefs, err := utils.LoadPrefs()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to load preferences: %v", err)
|
||||||
|
prefs = &utils.UserPrefs{} // Use default prefs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get saved password if the user has enabled saving
|
||||||
|
var savedPassword string
|
||||||
|
if prefs.SaveSudoPassword {
|
||||||
|
savedPassword, _ = utils.GetSudoPassword() // Ignore errors, just use empty string
|
||||||
|
}
|
||||||
|
|
||||||
// Show password dialog
|
// Show password dialog
|
||||||
passwordEntry := widget.NewPasswordEntry()
|
passwordEntry := widget.NewPasswordEntry()
|
||||||
passwordEntry.SetPlaceHolder("Enter your sudo password")
|
passwordEntry.SetPlaceHolder("Enter your sudo password")
|
||||||
|
passwordEntry.SetText(savedPassword) // Prefill with saved password if available
|
||||||
passwordEntry.Resize(fyne.NewSize(300, passwordEntry.MinSize().Height))
|
passwordEntry.Resize(fyne.NewSize(300, passwordEntry.MinSize().Height))
|
||||||
|
|
||||||
|
// Create checkbox for saving password
|
||||||
|
savePasswordCheck := widget.NewCheck("Save password securely in keychain", nil)
|
||||||
|
savePasswordCheck.SetChecked(prefs.SaveSudoPassword)
|
||||||
|
|
||||||
|
// Add status label if password is already saved
|
||||||
|
var statusLabel *widget.Label
|
||||||
|
if utils.HasSavedSudoPassword() {
|
||||||
|
statusLabel = widget.NewLabel("✓ Password already saved in keychain")
|
||||||
|
statusLabel.Importance = widget.LowImportance
|
||||||
|
}
|
||||||
|
|
||||||
// Create a container with proper sizing
|
// Create a container with proper sizing
|
||||||
passwordForm := widget.NewForm(widget.NewFormItem("Password:", passwordEntry))
|
passwordForm := widget.NewForm(widget.NewFormItem("Password:", passwordEntry))
|
||||||
passwordContainer := container.NewVBox(
|
|
||||||
|
var containerItems []fyne.CanvasObject
|
||||||
|
containerItems = append(containerItems,
|
||||||
widget.NewLabel("Enter your sudo password to start the RosettaX87 service:"),
|
widget.NewLabel("Enter your sudo password to start the RosettaX87 service:"),
|
||||||
passwordForm,
|
passwordForm,
|
||||||
|
savePasswordCheck,
|
||||||
)
|
)
|
||||||
passwordContainer.Resize(fyne.NewSize(400, 100))
|
|
||||||
|
if statusLabel != nil {
|
||||||
|
containerItems = append(containerItems, statusLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordContainer := container.NewVBox(containerItems...)
|
||||||
|
passwordContainer.Resize(fyne.NewSize(400, 140))
|
||||||
|
|
||||||
// Create the dialog variable so we can reference it in the callback
|
// Create the dialog variable so we can reference it in the callback
|
||||||
var passwordDialog dialog.Dialog
|
var passwordDialog dialog.Dialog
|
||||||
@@ -124,6 +158,25 @@ func StartRosettaX87Service(myWindow fyne.Window, updateAllStatuses func()) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle password saving/deleting based on checkbox state
|
||||||
|
shouldSavePassword := savePasswordCheck.Checked
|
||||||
|
if shouldSavePassword {
|
||||||
|
// Save password to keychain
|
||||||
|
if err := utils.SaveSudoPassword(password); err != nil {
|
||||||
|
log.Printf("Failed to save password to keychain: %v", err)
|
||||||
|
// Don't block the service start, just log the error
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Delete any existing saved password
|
||||||
|
utils.DeleteSudoPassword() // Ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update preferences
|
||||||
|
prefs.SaveSudoPassword = shouldSavePassword
|
||||||
|
if err := utils.SavePrefs(prefs); err != nil {
|
||||||
|
log.Printf("Failed to save preferences: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Close the dialog
|
// Close the dialog
|
||||||
passwordDialog.Hide()
|
passwordDialog.Hide()
|
||||||
|
|
||||||
@@ -360,3 +413,24 @@ func CleanupService() {
|
|||||||
serviceCmd = nil
|
serviceCmd = nil
|
||||||
servicePID = 0
|
servicePID = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearSavedPassword removes the saved password and shows a confirmation dialog
|
||||||
|
func ClearSavedPassword(myWindow fyne.Window) {
|
||||||
|
if !utils.HasSavedSudoPassword() {
|
||||||
|
dialog.ShowInformation("Password Status", "No password is currently saved.", myWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.ShowConfirm("Clear Saved Password",
|
||||||
|
"Are you sure you want to remove the saved password from the keychain?",
|
||||||
|
func(confirmed bool) {
|
||||||
|
if confirmed {
|
||||||
|
err := utils.DeleteSudoPassword()
|
||||||
|
if err != nil {
|
||||||
|
dialog.ShowError(fmt.Errorf("failed to clear saved password: %v", err), myWindow)
|
||||||
|
} else {
|
||||||
|
dialog.ShowInformation("Password Cleared", "The saved password has been removed from the keychain.", myWindow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, myWindow)
|
||||||
|
}
|
||||||
|
71
pkg/utils/keychain.go
Normal file
71
pkg/utils/keychain.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/zalando/go-keyring"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
serviceName = "TurtleSilicon"
|
||||||
|
accountName = "sudo_password"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SaveSudoPassword securely stores the sudo password in the system keychain
|
||||||
|
func SaveSudoPassword(password string) error {
|
||||||
|
if password == "" {
|
||||||
|
return fmt.Errorf("password cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := keyring.Set(serviceName, accountName, password)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to save password to keychain: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Password saved securely to keychain")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSudoPassword retrieves the saved sudo password from the system keychain
|
||||||
|
func GetSudoPassword() (string, error) {
|
||||||
|
password, err := keyring.Get(serviceName, accountName)
|
||||||
|
if err != nil {
|
||||||
|
// If the password doesn't exist, return empty string instead of error
|
||||||
|
if err == keyring.ErrNotFound {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("failed to retrieve password from keychain: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return password, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSudoPassword removes the saved sudo password from the system keychain
|
||||||
|
func DeleteSudoPassword() error {
|
||||||
|
err := keyring.Delete(serviceName, accountName)
|
||||||
|
if err != nil {
|
||||||
|
// If the password doesn't exist, that's fine
|
||||||
|
if err == keyring.ErrNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to delete password from keychain: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Password removed from keychain")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSavedSudoPassword checks if a sudo password is saved in the keychain
|
||||||
|
func HasSavedSudoPassword() bool {
|
||||||
|
_, err := keyring.Get(serviceName, accountName)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPasswordStatusText returns a user-friendly status text for password saving
|
||||||
|
func GetPasswordStatusText() string {
|
||||||
|
if HasSavedSudoPassword() {
|
||||||
|
return "Password saved in keychain"
|
||||||
|
}
|
||||||
|
return "No password saved"
|
||||||
|
}
|
@@ -11,6 +11,7 @@ type UserPrefs struct {
|
|||||||
TurtleWoWPath string `json:"turtlewow_path"`
|
TurtleWoWPath string `json:"turtlewow_path"`
|
||||||
CrossOverPath string `json:"crossover_path"`
|
CrossOverPath string `json:"crossover_path"`
|
||||||
EnvironmentVariables string `json:"environment_variables"`
|
EnvironmentVariables string `json:"environment_variables"`
|
||||||
|
SaveSudoPassword bool `json:"save_sudo_password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPrefsPath() (string, error) {
|
func getPrefsPath() (string, error) {
|
||||||
|
Reference in New Issue
Block a user