The iOS app of Termoak, the open-source SSH client with an optional self-hosted server. It is SwiftUI (iOS 15+) on top of the same Rust engine as the desktop app and the CLI, through UniFFI, with SwiftTerm as the terminal emulator.
The same as the Android app:
- Accounts: several at once, on the official server or your own, with in-app sign-up. The app also works without a server, only on the device.
- Vaults shared with people and teams (Editor or Use only), with sync.
- Hosts: search, groups, favorites, multi-select and a full editor, with logos (the detected system's or one you choose).
- Terminal with tabs: local SSH and Telnet, persistent server sessions,
snippets, copy/paste, the latency in the bar, and split view with
broadcast input on iPad. Quick connect (⌘K) takes
user@host:portandtelnet://host:porttoo. - Live session sharing: invite by email, team or link; one person types
at a time, with a waiting room; join with
termoak://joinlinks. - AI with approvals, keychain, SFTP files, port forwarding and settings.
- Hardware keyboard and trackpad (iPad and iPhone): Ctrl, Option as Meta (or for your layout's characters), ⌘. as Esc, xterm arrows and function keys; app shortcuts listed when ⌘ is held (⌘T/⌘K, ⌘W, ⌘⇧]/⌘⇧[, Ctrl+Tab, ⌘1…⌘9, ⌘F, ⌘+/⌘-/⌘0, ⌘,); arrow keys in the host list and the files; wheel to programs that take the mouse, drag to select, secondary click.
- iPad desktop layout: in full screen and in big Split View or Stage Manager windows the app looks and works like the desktop app: tabs on top (Home, one per terminal, drag to reorder), a collapsible sidebar (⌃⌘S) with the vault, server and app sections, hosts as cards in a grid with the editor in a side panel, and the desktop's terminal toolbar. Narrow windows keep the phone layout. Settings → Appearance → "Layout on wide screens" can keep the phone layout, or use the desktop one on an iPhone Plus or Pro Max in landscape too.
iOS cuts local connections shortly after you leave the app (the app asks for a few minutes of grace time). For long jobs, use server sessions.
On a Mac with Xcode, rustup and XcodeGen:
git clone --recurse-submodules https://github.com/TermoakSSH/mobile-ios
cd mobile-ios
brew install xcodegen
xcodebuild -downloadComponent MetalToolchain # SwiftTerm compiles Metal shaders
scripts/build-ipa.sh # → an unsigned .ipa in dist/ios/The .ipa is unsigned: sign it with your own certificate (Sideloadly,
AltStore, codesign with a profile...). Without a signature the Keychain
does not work and the app cannot open the vault (error -34018).
This repository includes TermoakSSH/core
as a git submodule in core/, pinned to a release tag. The native engine
(termoak-ffi) is built from it as an xcframework, and the Swift package
TermoakKit comes from core/bindings/swift.
git submodule update --init # in an existing clone
core/scripts/build-ios.sh debug # the engine (once, and after every core update)
xcodegen generate # the Xcode project, from project.yml
open Termoak.xcodeprojTermoak.xcodeproj is generated from project.yml and is not committed:
edit project.yml and run xcodegen generate again.
scripts/build-ipa.sh only builds the engine (release) when
core/bindings/swift/termoak_ffiFFI.xcframework is missing.
The engine binary is not versioned, so it has to be rebuilt every time the submodule moves:
git -C core fetch --tags && git -C core checkout vX.Y.Z
core/scripts/build-ios.sh debug
git add core && git commit -m "core vX.Y.Z"After a git pull that moves core, run git submodule update and
rebuild the engine too.
Signing.xcconfig is the one place for the Apple Team ID
(DEVELOPMENT_TEAM). The app asks for the Associated Domains capability
(Termoak/Termoak.entitlements: applinks:termoak.com and
applinks:next.termoak.com) so https://termoak.com/join/<token> links open
it. A personal (free) team can't sign with that capability: empty
CODE_SIGN_ENTITLEMENTS in Signing.xcconfig to build with one. Once the
Team ID is filled in, scripts/apple-app-site-association.sh <file> writes
the apple-app-site-association the servers publish at
/.well-known/apple-app-site-association (public-web:
site/.well-known/apple-app-site-association).
The app registers its APNs device token on every signed-in account (each
server notifies its own events: AI approvals, sessions shared with you, join
and keyboard requests) and unregisters it before signing out of one
(Termoak/Model/PushNotifications.swift). It stays off until the APNs
credentials exist: to turn it on, add the Push Notifications capability
(aps-environment in Termoak/Termoak.entitlements), set TermoakPush: true
in project.yml (the app's Info.plist properties) and configure APNs on the
servers (server docs/DEPLOYMENT.md, push notifications). Debug builds
register as APNs sandbox tokens.
The "official server" button signs in to https://termoak.com. To test
against another server (the staging server, for example), build the engine
with TERMOAK_OFFICIAL_SERVER=https://next.termoak.com core/scripts/build-ios.sh.
Any server also works through "Use your own server".
UnitTests/ checks the pure logic, such as what each key of a hardware
keyboard sends (Termoak/Model/HardwareKeys.swift), the Telnet port and
telnet:// addresses, host logos and the layout of wide windows. The target compiles the
files it tests, so it needs neither the app nor an sshd:
xcodebuild test -project Termoak.xcodeproj -scheme Termoak -only-testing:TermoakTests \
-destination 'platform=iOS Simulator,name=iPhone 17' -skipPackagePluginValidationUITests/ goes through the key bar, the cursor gesture and the quick access
panel against a test sshd, and saves screenshots. They only run with
xcodebuild test and are not part of the .ipa:
TEST_RUNNER_TEST_DIR=/path/with/client/key \
TEST_RUNNER_TEST_HOST=127.0.0.1:2222:user \
xcodebuild test -project Termoak.xcodeproj -scheme Termoak \
-destination 'platform=iOS Simulator,name=iPhone 17' -skipPackagePluginValidationTEST_DIR holds the private key client (the screenshots are left there)
and TEST_HOST is address:port:user.
| Symptom | Cause and fix |
|---|---|
Hundreds of cannot find 'uniffi_termoak_ffi_checksum_…' in scope |
The engine xcframework is older than the bindings in core/. Run core/scripts/build-ios.sh debug. |
Validate plug-in "SwiftTermBuildInfoPlugin" fails |
SwiftTerm's build plugin needs to be trusted: accept it in Xcode, or pass -skipPackagePluginValidation to xcodebuild. |
| Metal shader errors while building SwiftTerm | xcodebuild -downloadComponent MetalToolchain |
| The app cannot open the vault (error -34018) | The app is not signed, so the Keychain is unavailable. Sign the .ipa. |
The app is released as the ios-vX.Y.Z GitHub release (MARKETING_VERSION
in project.yml), with the unsigned .ipa:
scripts/release-local.sh version ios X.Y.Z # also bumps the build number
scripts/release-local.sh build ios # on a Mac: dist/ios/
scripts/release-local.sh publish ios # without a build, it creates the release empty- Mobile apps and the FFI layer (in core)
- Internationalization: the strings are in
Termoak/Localizable.xcstrings
| Repository | Contents |
|---|---|
| TermoakSSH/core | Shared crates (SSH engine, vault, API client, AI engine, FFI bindings, updates) and the termoak CLI |
| TermoakSSH/server | termoak-server: HTTP/WebSocket API, basic web app, deployment files |
| TermoakSSH/desktop | Desktop app (GPUI) for Windows, Linux and macOS |
| TermoakSSH/mobile-android | Android app (Jetpack Compose) |
| TermoakSSH/mobile-ios | iOS app (SwiftUI) |
| TermoakSSH/public-web | Public website of termoak.com: landing, pricing and downloads |
Bug reports, fixes, features and translations are welcome: see CONTRIBUTING.md. Translating Termoak into your language needs no programming: copy the English strings file of an app, translate it and open a pull request (docs/I18N.md).
Copyright © Ohz Digital SL.
Termoak is free software released under the GNU Affero General Public License v3.0 (AGPL-3.0-only).
"Termoak" and the Termoak logo are trademarks of Ohz Digital SL and are not covered by the code license: see TRADEMARK.md.