// cmd/khimera-portable/main.go package main import ( "fmt" "log" "os" "path/filepath" "runtime" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" "khimera/addressbook" "khimera/identity" "khimera/session" "khimera/transport" ) // PortableApp represents the portable Khimera application type PortableApp struct { config *PortableConfig identity *identity.Identity addressBook *addressbook.AddressBook sessionMgr *session.SessionManager transportMgr *transport.TransportManager } // PortableConfig holds portable mode configuration type PortableConfig struct { IsPortable bool BaseDir string DataDir string TorDir string TempDir string IdentityPath string ContactsPath string ConfigPath string IsUSB bool IsNetworkShare bool Platform string } func main() { fmt.Println("╔════════════════════════════════════════╗") fmt.Println("║ KHIMERA PORTABLE v0.9 ║") fmt.Println("║ Privacy-First P2P Messenger ║") fmt.Println("╚════════════════════════════════════════╝") fmt.Println() // Detect and setup portable mode config, err := detectPortableMode() if err != nil { log.Fatalf("Failed to initialize portable mode: %v", err) } // Display portable status displayPortableStatus(config) // Initialize portable app portableApp := &PortableApp{ config: config, } // Setup data directories if err := portableApp.setupDirectories(); err != nil { log.Fatalf("Failed to setup directories: %v", err) } // Start GUI portableApp.startGUI() } // detectPortableMode detects portable environment func detectPortableMode() (*PortableConfig, error) { // When running as AppImage, APPIMAGE env var points to the actual .AppImage file // os.Executable() would return the read-only mount path inside /tmp/.mount_* var baseDir string if appImagePath := os.Getenv("APPIMAGE"); appImagePath != "" { baseDir = filepath.Dir(appImagePath) } else { exe, err := os.Executable() if err != nil { return nil, fmt.Errorf("failed to get executable path: %w", err) } baseDir = filepath.Dir(exe) } config := &PortableConfig{ IsPortable: true, BaseDir: baseDir, DataDir: filepath.Join(baseDir, "data"), TorDir: filepath.Join(baseDir, "tor"), TempDir: filepath.Join(baseDir, "temp"), Platform: runtime.GOOS, } // Set file paths config.IdentityPath = filepath.Join(config.DataDir, "identity.enc") config.ContactsPath = filepath.Join(config.DataDir, "contacts.enc") config.ConfigPath = filepath.Join(config.DataDir, "config.json") // Detect if running from USB config.IsUSB = isRemovableDrive(baseDir) config.IsNetworkShare = isNetworkPath(baseDir) return config, nil } // isRemovableDrive checks if path is on removable media func isRemovableDrive(path string) bool { switch runtime.GOOS { case "windows": return isWindowsRemovable(path) case "linux": return isLinuxRemovable(path) default: return false } } // isWindowsRemovable checks for Windows removable drives func isWindowsRemovable(path string) bool { // Simplified check - in production use Windows API drive := filepath.VolumeName(path) // Check if drive letter is in typical USB range (E: to Z:) if len(drive) > 0 { letter := drive[0] return letter >= 'E' && letter <= 'Z' } return false } // isLinuxRemovable checks for Linux removable media func isLinuxRemovable(path string) bool { // Check if path contains /media or /mnt (typical USB mount points) return filepath.HasPrefix(path, "/media") || filepath.HasPrefix(path, "/mnt") } // isNetworkPath checks if running from network share func isNetworkPath(path string) bool { switch runtime.GOOS { case "windows": return filepath.HasPrefix(path, "\\\\") case "linux": // Check for NFS/SMB mounts (simplified) return false } return false } // displayPortableStatus shows portable mode information func displayPortableStatus(config *PortableConfig) { fmt.Println("📁 Portable Mode Configuration") fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") fmt.Printf(" Platform: %s\n", config.Platform) fmt.Printf(" Base Dir: %s\n", config.BaseDir) fmt.Printf(" Data Dir: %s\n", config.DataDir) fmt.Printf(" Running from: ") if config.IsUSB { fmt.Println("USB Drive ✓") } else if config.IsNetworkShare { fmt.Println("Network Share ✓") } else { fmt.Println("Local Drive") } fmt.Println() } // setupDirectories creates required directories func (pa *PortableApp) setupDirectories() error { dirs := []string{ pa.config.DataDir, pa.config.TorDir, pa.config.TempDir, } for _, dir := range dirs { if err := os.MkdirAll(dir, 0700); err != nil { return fmt.Errorf("failed to create directory %s: %w", dir, err) } } fmt.Println("✓ Directories initialized") return nil } // startGUI launches the portable GUI application func (pa *PortableApp) startGUI() { myApp := app.New() myWindow := myApp.NewWindow("Khimera Portable v0.9") // Welcome screen welcomeLabel := widget.NewLabel("Welcome to Khimera Portable") welcomeLabel.Alignment = fyne.TextAlignCenter infoLabel := widget.NewLabel(fmt.Sprintf( "Running from: %s\nData directory: %s", pa.config.BaseDir, pa.config.DataDir, )) infoLabel.Wrapping = fyne.TextWrapWord // Identity section var passphraseEntry *widget.Entry var statusLabel *widget.Label statusLabel = widget.NewLabel("No identity loaded") passphraseEntry = widget.NewPasswordEntry() passphraseEntry.SetPlaceHolder("Enter passphrase...") // Load/Create identity button loadBtn := widget.NewButton("Load Identity", func() { passphrase := passphraseEntry.Text if passphrase == "" { statusLabel.SetText("⚠ Please enter a passphrase") return } // Try to load existing identity if _, err := os.Stat(pa.config.IdentityPath); err == nil { id, err := identity.LoadIdentity(pa.config.IdentityPath, []byte(passphrase)) if err != nil { statusLabel.SetText("⚠ Failed to load identity (wrong passphrase?)") return } pa.identity = id statusLabel.SetText("✓ Identity loaded: " + id.GetPublicKeyHex()[:16] + "...") } else { // Create new identity id, err := identity.NewIdentity() if err != nil { statusLabel.SetText("⚠ Failed to create identity") return } if err := id.Save(pa.config.IdentityPath, []byte(passphrase)); err != nil { statusLabel.SetText("⚠ Failed to save identity") return } pa.identity = id statusLabel.SetText("✓ New identity created: " + id.GetPublicKeyHex()[:16] + "...") } // Initialize other components pa.initializeComponents() }) // Quit button quitBtn := widget.NewButton("Quit", func() { pa.cleanup() myApp.Quit() }) // Layout content := container.NewVBox( welcomeLabel, widget.NewSeparator(), infoLabel, widget.NewSeparator(), widget.NewLabel("Identity Management:"), passphraseEntry, loadBtn, statusLabel, widget.NewSeparator(), quitBtn, ) myWindow.SetContent(content) myWindow.Resize(fyne.NewSize(600, 400)) myWindow.CenterOnScreen() myWindow.ShowAndRun() } // initializeComponents initializes core components after identity is loaded func (pa *PortableApp) initializeComponents() error { // Initialize session manager pa.sessionMgr = session.NewSessionManager() // Initialize transport manager pa.transportMgr = transport.NewTransportManager() // Try to load address book if _, err := os.Stat(pa.config.ContactsPath); err == nil { // Load existing contacts fmt.Println("✓ Address book loaded") } else { // Create new address book pa.addressBook = addressbook.NewAddressBook() fmt.Println("✓ New address book created") } fmt.Println("✓ Components initialized") return nil } // cleanup performs cleanup on application exit func (pa *PortableApp) cleanup() { fmt.Println("\n🧹 Cleaning up...") // Secure wipe sessions if pa.sessionMgr != nil { pa.sessionMgr.SecureWipeAll() fmt.Println(" ✓ Sessions wiped") } // Close transports if pa.transportMgr != nil { pa.transportMgr.Close() fmt.Println(" ✓ Transports closed") } // Wipe identity from memory if pa.identity != nil { pa.identity.SecureWipe() fmt.Println(" ✓ Identity wiped from memory") } // Clean temp directory if pa.config.TempDir != "" { os.RemoveAll(pa.config.TempDir) fmt.Println(" ✓ Temp files removed") } fmt.Println("✓ Cleanup complete") }