- Kotlin 76%
- JavaScript 17%
- Zig 7%
- Nix <1%
- Shell <1%
Eternal Terminal Android #
A modern Android client for Eternal Terminal with Material You theming, automatic reconnection, and secure authentication.
Features #
- Host Management: Save and organize your Eternal Terminal connections
- Material You Design: Dynamic colors and modern Android UI
- SSH Authentication: Password and private key support
- Automatic Reconnection: ET's signature feature - stays connected even when your network changes
- Terminal Input: Full keyboard support including special keys, arrow keys, and control sequences
- Biometric Security: Optional fingerprint/face unlock per host
- Background Connections: Maintain connections in the background with foreground service
Architecture #
This is a pure Kotlin Android app implementing the Eternal Terminal protocol directly:
- Protocol Layer: Pure Kotlin implementation of ET protocol (SSH handshake, TCP connection, XSalsa20+Poly1305 crypto, reconnection)
- UI Layer: Jetpack Compose with Material You theming
- Data Layer: Room database for host configurations and settings
- Security: Android Keystore for private keys, BiometricPrompt for authentication
Why Pure Kotlin? #
The original plan was to wrap the ET C++ client via JNI, but this proved extremely complex because:
- ET is designed as a standalone binary, not a library
- Heavy dependencies (protobuf, libsodium, openssl, boost)
- Executes SSH as a subprocess for authentication
- Complex build system with vcpkg cross-compilation
Instead, we implemented the ET wire protocol directly in Kotlin, which provides:
- Better Android integration
- Easier maintenance
- Smaller APK size
- Full control over the connection lifecycle
Building #
Prerequisites #
- Nix package manager with flakes enabled
- direnv (optional but recommended)
Setup #
- Clone the repository:
git clone <repository-url>
cd et-android
- Enter the development shell:
nix develop
# Or if using direnv:
direnv allow
- Build the project:
./gradlew build
- Install debug APK:
./gradlew installDebug
Project Structure #
et-android/
├── app/
│ ├── src/main/
│ │ ├── java/io/et/android/
│ │ │ ├── protocol/ # ET protocol implementation
│ │ │ │ ├── EtProtocol.kt # Main connection handler
│ │ │ │ ├── EtCrypto.kt # XSalsa20 + Poly1305 encryption
│ │ │ │ ├── SshSetup.kt # SSH handshake
│ │ │ │ └── TerminalInputHandler.kt # Keyboard input
│ │ │ ├── data/
│ │ │ │ ├── db/ # Room database
│ │ │ │ └── model/ # Host, SshKey entities
│ │ │ ├── ui/
│ │ │ │ ├── screens/ # Compose screens
│ │ │ │ └── theme/ # Material You theme
│ │ │ ├── viewmodel/ # Hilt ViewModels
│ │ │ ├── di/ # Dependency injection
│ │ │ ├── service/ # Background connection service
│ │ │ ├── MainActivity.kt
│ │ │ └── EtAndroidApp.kt
│ │ ├── proto/ # ET protobuf definitions
│ │ └── res/ # Android resources
│ └── build.gradle.kts
├── identity-server/ # Passkey.pub identity and public-key directory service
│ └── src/main/kotlin/io/et/identity/
├── flake.nix # Nix development environment
├── .envrc # direnv configuration
└── PROJECT_PLAN.md # Detailed project plan
Protocol Implementation #
The ET protocol implementation follows the official specification:
- SSH Handshake: Uses JSch to SSH into the host and run
etterminalto get credentials - TCP Connection: Connects to port 2022 (default)
- Encryption: XSalsa20 stream cipher + Poly1305 MAC using Bouncy Castle
- Message Framing: Length-prefixed protobuf messages
- Reconnection: Sequence number tracking and catchup buffers
Development Status #
Completed (MVP) #
In Progress #
Planned #
Passkey.pub Identity Server #
The identity-server module is a self-hostable passkey-backed SSH public-key directory. Production deployments should set:
ET_IDENTITY_RP_ID=identity.example.com
ET_IDENTITY_ORIGINS=https://identity.example.com
ET_IDENTITY_ANDROID_SHA256_CERT_FINGERPRINTS=<release signing certificate SHA-256>
ET_IDENTITY_DB=/var/lib/passkeypub/et-identity.sqlite
Use HTTPS for every non-local deployment. The Android client rejects non-HTTPS identity URLs except localhost development URLs, stores identity sessions in encrypted preferences, and excludes those preferences from backup.
Contributing #
This is an open-source project. Contributions are welcome!
License #
MIT/Apache 2.0 (TBD)