chore: clean up .gitignore, track gradle-wrapper.jar, drop .zcode/
- Rewrite .gitignore with proper sections; add ignores for common Android/IDE/OS artifacts (*.so, *.hprof, app/release/, Thumbs.db, .vscode/, *.swp). - Track gradle/wrapper/gradle-wrapper.jar so ./gradlew works after a fresh clone (the previous negation rule was a no-op without a prior *.jar ignore, so the jar was never committed). - Stop tracking the local .zcode/ tooling directory (kept on disk).
This commit is contained in:
parent
66417cc194
commit
084fec3948
3 changed files with 36 additions and 80 deletions
47
.gitignore
vendored
47
.gitignore
vendored
|
|
@ -1,17 +1,42 @@
|
|||
*.iml
|
||||
.gradle/
|
||||
.idea/
|
||||
.kotlin/
|
||||
local.properties
|
||||
.DS_Store
|
||||
build/
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aab
|
||||
*.ap_
|
||||
*.dex
|
||||
|
||||
# Keep the gradle wrapper jar (it IS the build tool bootstrap).
|
||||
# Build output (Gradle / AGP)
|
||||
build/
|
||||
.gradle/
|
||||
.kotlin/
|
||||
captures/
|
||||
|
||||
# Native build artifacts
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.so
|
||||
|
||||
# IDE / Editor
|
||||
*.iml
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Local config (machine-specific SDK path, signing keys, etc.)
|
||||
local.properties
|
||||
|
||||
# Profiling / heap dumps
|
||||
*.hprof
|
||||
|
||||
# Release build output
|
||||
app/release/
|
||||
|
||||
# Local tooling (ZCode session/plans — not part of the project)
|
||||
.zcode/
|
||||
|
||||
# Gradle wrapper: the jar IS the build bootstrap and must be committed so
|
||||
# `./gradlew` works after a fresh clone. Keep it despite any broad ignores above.
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
# SSH Connection Manager (Android, Kotlin + Jetpack Compose) — Bauplan
|
||||
|
||||
## Wichtige Vorbemerkung: 1Password auf Android
|
||||
**Echte native 1Password-SSH-Integration ist auf Android technisch nicht möglich** — der 1Password SSH Agent und die SDKs sind Desktop-only (macOS/Windows/Linux), es gibt keine Kotlin/Java-API und keinen Android-Agent, nur einen offenen [Feature-Request](https://www.1password.community/developers-69/feature-request-android-and-ssh-19991). Du hast dich für **„Nur Datei-Import"** entschieden: Private Keys werden per Storage-Access-Framework importiert, AES-verschlüsselt (Android Keystore) gespeichert. Der praktische 1P-Workflow ist dann: Key auf dem Desktop aus 1Password exportieren → aufs Handy übertragen → in der App importieren. Kein 1Password-Bezug im UI.
|
||||
|
||||
## Stack & Abhängigkeiten (je current stable, 2026)
|
||||
- **Kotlin 2.0+ (K2)**, AGP 8.7+, JDK 17, Compile/Target SDK 35, **Min SDK 26**
|
||||
- **Jetpack Compose** (BOM), Material 3, dunkles Theme, Single-Activity + Navigation-Compose
|
||||
- **Hilt** (DI, via KSP), **Room** 2.6+ (via KSP), Coroutines + Flow
|
||||
- **sshj 0.39.x** — SSH-Verbindung, Auth (Passwort + Public Key), Shell/PTY
|
||||
- **Termux `terminal-emulator`** via JitPack (`v0.118.3`) — VT100/xterm-Emulation (state machine, ANSI-Parsing, Buffer)
|
||||
- **Bouncy Castle** (`bcprov-jdk18on`) — registriert als Provider, damit sshj OpenSSH-/Ed25519-Keys liest
|
||||
- **Android Keystore** — AES-GCM-Schlüssel zur Feldverschlüsselung (Passwörter, private Keys)
|
||||
|
||||
## Architektur (Clean Layering, Paket-Struktur)
|
||||
```
|
||||
core/crypto KeystoreCrypto (AES-GCM Ver-/Entschlüsselung sensitiver Felder)
|
||||
data Room: AppDatabase, Entities (Host, SshKey, Group), DAOs, Repositories, DI
|
||||
domain/model Host, SshKey, Group, AuthMethod (sealed)
|
||||
ssh SshConnectionManager (sshj SSHClient, Auth, Shell), SshSession, KeyLoader, DI
|
||||
terminal SshTerminalBridge, TerminalShell, KeyEncoder, ui/TerminalScreen (Canvas), ViewModel
|
||||
ui theme, nav (AppNavGraph, Routes), hostlist, hosteditor, keylist, keyimport, components
|
||||
```
|
||||
|
||||
## Datenmodell (Room)
|
||||
- **Host**: id, name, hostName, port, userName, groupId?, authType (PASSWORD/KEY), passwordCipher?, keyId?, lastConnected?
|
||||
- **SshKey**: id, name, keyType, fingerprint, privateKeyCipher, hasPassphrase, createdAt
|
||||
- **Group** (optional): id, name, sortIndex — zum Ordnen von Hosts
|
||||
|
||||
## Sicherheits-Workflow
|
||||
1. Key-Import via SAF (`ACTION_OPEN_DOCUMENT`, MIME `*/*`), Datei als String lesen.
|
||||
2. Mit sshj parsen → Typ + Fingerprint bestimmen; ggf. Passphrase abfragen.
|
||||
3. Private Key mit Keystore-AES-GCM verschlüsseln, Ciphertext in Room speichern, Klartext sofort verwerfen.
|
||||
4. Beim Verbinden: entschlüsseln → in den Arbeitsspeicher → sshj laden → verbinden → verwerfen.
|
||||
5. Host-Passwörter analog verschlüsselt.
|
||||
|
||||
## Terminal-Integration (Kernbaustein)
|
||||
Bidirektionale Brücke zwischen **sshj-Shell-Stream** und **Termux-`TerminalEmulator`**:
|
||||
- sshj `Shell` → Output-Bytes in Reader-Thread → `TerminalEmulator.process(bytes)` → aktualisiert `TerminalBuffer`.
|
||||
- Tastatur-Eingabe → `KeyEncoder` (Pfeiltasten etc. → ESC-Sequenzen) → `SshTerminalBridge.write()` → sshj Shell-OutputStream.
|
||||
- `TerminalEmulator` direkt verwendet (nicht `TerminalSession`, das lokale Prozesse startet).
|
||||
- **Renderer**: eigene dünne `Canvas`-Composable, die `TerminalBuffer` rendert — orientiert an Termux' `TerminalRenderer`. Scope: monospaces Raster, 16/256-Farben-Palette, bold/italic/underline/inverse, Cursor, Scrollback, Alternate Screen. Deckt reale Server-Sessions (`vim`, `htop`, Shell) ab; exotisches (Sixel/Maus) bewusst out-of-scope.
|
||||
- IME-Anpassung (sichtbare Soft-Tastatur, Ctrl/Alt/Esc/Tastatur-Erweiterungsleiste) als Bestandteil.
|
||||
|
||||
## UI-Screens (Compose)
|
||||
1. **HostList** — gruppierte Liste, Suche, Connect-Action, Swipe-to-delete; FAB „Host hinzufügen".
|
||||
2. **HostEditor** — Felder für Name/Host/Port/User, Auth-Auswahl (Passwort vs. Key-Auswahl), Group-Zuordnung, „Verbindung testen".
|
||||
3. **KeyList** — Keys mit Fingerprint/Typ, Import-Button, Löschen.
|
||||
4. **KeyImport** — Datei wählen (SAF), Name, Passphrase, Vorschau-Validierung.
|
||||
5. **Terminal** — Fullscreen Canvas-Terminal + Tastaturleiste; Connection-Status, Disconnect.
|
||||
|
||||
## Build-Reihenfolge (Phasen)
|
||||
1. **Scaffold**: Gradle (Version Catalog `libs.versions.toml`), Manifest, Application (Hilt), MainActivity, Theme, Navigation-Gerüst.
|
||||
2. **Daten & Crypto**: Room-Entities/DAOs/DB, Repos, `KeystoreCrypto`, DI-Module.
|
||||
3. **UI CRUD**: HostList, HostEditor, KeyList, KeyImport (SAF + sshj-Parse + Verschlüsselung). Ab hier lauffähiger Manager.
|
||||
4. **SSH-Layer**: `SshConnectionManager` (sshj Connect + Auth Passwort/Key), „Verbindung testen".
|
||||
5. **Terminal**: Brücke + `TerminalEmulator`-Wiring + Canvas-Renderer + Tastatur. End-to-end SSH-Session im Terminal.
|
||||
6. **Robustheit**: Connection-Lifecycle im process-globalen Singleton (überlebt Konfigwechsel), Fehler-/Timeout-Handling, Später: Foreground-Service für echte Hintergrund-Persistenz (als Erweiterung markiert).
|
||||
|
||||
## Ehrliche Hinweise / Risiken
|
||||
- **Terminal-Renderer**: komplette xterm-Fidelity ist kein Ziel; angestrebt ist ein robuster Renderer für typische Shell/`vim`/`htop`-Nutzung. Polarisierung (Unicode-Width, Truetype-Monospace-Messing) inkrementell.
|
||||
- **Hintergrund-Persistenz** echter Sessions braucht einen Foreground-Service (mit Notification) — im MVP wird die Session nur vor Config-Wechsel geschützt; der FG-Service ist klar als Folge-Task markiert.
|
||||
- **Kein echtes 1Password**: wird im UI ehrlich nicht suggeriert.
|
||||
|
||||
## Was du am Ende hast
|
||||
Ein lauffähiges Android-App-Projekt: Hosts/Keys verwalten (verschlüsselt gespeichert), über Passwort oder importierten Key verbinden, und ein **interaktives Compose-Terminal** für SSH-Sessions. Bauen mit `./gradlew assembleDebug`.
|
||||
|
||||
## Umsetzungshinweis
|
||||
Ich lege das komplette Projekt als neues Gradle-Projekt an (es ist aktuell ein leeres Verzeichnis) und implementiere alle Phasen. Bei sehr umfangreichen einzelnen Klassen (Terminal-Renderer, SshConnectionManager) halte ich sie fokunktional, aber schlank und klar kommentiert.
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue