62 lines
3.5 KiB
Markdown
62 lines
3.5 KiB
Markdown
# Architecture
|
|
|
|
```
|
|
Sources/RemminaMac/
|
|
RemminaMacApp.swift # @main, WindowGroup, loads ProfileStore
|
|
Models/ConnectionProfile.swift # ProtocolType + ConnectionProfile + StoredSSHKey (Codable)
|
|
Models/ProfileValidation.swift # input checks + trimming (pure, tested)
|
|
Stores/ProfileStore.swift # ObservableObject, JSON persistence, filter/search/groups
|
|
Security/KeychainHelper.swift # SecItemAdd/Copy/Delete wrapper
|
|
Services/ConnectionServices.swift # SSHService (ssh args, secrets, sshpass, agent) + RDPService
|
|
Services/PTYSession.swift # real PTY session (posix_openpt), stderr pipe, TerminalManager
|
|
Services/AnsiParser.swift # ANSI/SGR parser for diagnostics buffer (pure, tested)
|
|
Services/TCPCheck.swift # TCP pre-check via Network.framework
|
|
Services/HostKeyService.swift # keyscan/fingerprint/accept/revoke via system ssh tools
|
|
Services/TerminalTheme.swift # Dracula palette (SwiftUI + NSColor + SwiftTerm)
|
|
Views/ContentView.swift # NavigationSplitView: sidebar + tabs + status bar
|
|
Views/SessionView.swift # per-session detail
|
|
Views/SwiftTermView.swift # full terminal emulator view (SwiftTerm, Dracula)
|
|
Views/ProfileEditorView.swift # new/edit form + Keychain wiring + validation
|
|
Views/SSHKeysView.swift # key list, import, ssh-keygen
|
|
Views/HostKeySheet.swift # fingerprint popup (accept/decline, MITM warning)
|
|
Views/AboutView.swift # about sheet (contributor, repo link)
|
|
Sources/ctty-run/
|
|
ctty-run.c # gives the ssh child a controlling terminal
|
|
# (double-fork: setsid + slave open + exec)
|
|
```
|
|
|
|
## Data flow
|
|
1. `RemminaMacApp` creates `ProfileStore` as `@StateObject`, injects as `environmentObject`.
|
|
2. `ProfileStore.load()` reads `profiles.json` + `sshkeys.json`, else sample data.
|
|
3. `ContentView` filters via `searchText`/`selectedGroup`; `selection` drives detail.
|
|
4. `connect(_:)` stamps `lastConnected`, opens a tab. The terminal view runs:
|
|
TCP pre-check → host-key status (popup if unknown/changed) → `PTYSession.start()`.
|
|
5. `PTYSession` spawns via `ctty-run` (controlling terminal): stdin/stdout on the
|
|
pty slave, stderr on a loss-free pipe. Output feeds SwiftTerm (`onData`) and
|
|
the diagnostics buffer (`lines`, used by the error banner + report).
|
|
6. Keyboard flows from SwiftTerm's delegate straight into the pty master
|
|
(`sendBytes`); window resizes propagate via `TIOCSWINSZ` (`setWinsize`).
|
|
7. Secrets only via `KeychainHelper`; JSON holds account references.
|
|
`TerminalManager` caches one session per profile and reconfigures on any
|
|
config change (fingerprint incl. secret + sshpass presence).
|
|
|
|
## Key files
|
|
- `Stores/ProfileStore.swift` — paths + `load()`/`save()`
|
|
- `Models/ConnectionProfile.swift` — protocol defaults (ports, icons)
|
|
- `Services/PTYSession.swift` — spawn, lifecycle, `TerminalManager`
|
|
- `Services/HostKeyService.swift` — fingerprint flow
|
|
- `Views/SwiftTermView.swift` — emulator wiring + Dracula theme
|
|
- `Views/ContentView.swift` — Remmina layout
|
|
|
|
## Verification strategy
|
|
Logic is verified with throwaway `swiftc` harnesses in `/tmp` (never committed):
|
|
ANSI parser, PTY roundtrips, real `ssh` runs, Keychain roundtrips, TCP checks,
|
|
host-key flow against a local paramiko test server, SwiftTerm core headless
|
|
(cursor addressing, alt-screen, clear), view-layer stress. See `docs/ERRORS.md`.
|
|
|
|
## Next steps
|
|
1. Embedded RDP (FreeRDP bridge) instead of `Process`.
|
|
2. Integrated SFTP browser.
|
|
3. Export/Import buttons + drag & drop for keys.
|
|
4. Tray / favorites / folder badges like Remmina.
|