1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
|
// 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")
}
|