Skip to content

Commit c445796

Browse files
authored
Add secure iOS and Android gateway clients (#15)
* feat: add secure mobile gateway clients Signed-off-by: Joseph Yaksich <gitcommit90@users.noreply.github.com> * test: tolerate missing CI browser Signed-off-by: Joseph Yaksich <gitcommit90@users.noreply.github.com> --------- Signed-off-by: Joseph Yaksich <gitcommit90@users.noreply.github.com> Co-authored-by: Joseph Yaksich <gitcommit90@users.noreply.github.com>
1 parent 9a603fb commit c445796

99 files changed

Lines changed: 3477 additions & 69 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.0.13] - 2026-07-26
11+
12+
### Added
13+
14+
- iPhone, iPad, and Android now have thin Capacitor gateway clients for an
15+
already configured 1Helm host. The connection screen accepts an HTTPS server
16+
address, username, and password, confirms the host's explicit mobile
17+
compatibility contract, and never exposes first-run host setup.
18+
- Android ships as a directly distributed, signed universal APK with a stable
19+
application ID and permanent release certificate. Release builds fail
20+
closed without external signing material and verify alignment, package and
21+
version metadata, v2/v3 signatures, and native ABI coverage.
22+
- iOS has an App Store archive/export path with iPhone and iPad support, stable
23+
bundle and callback identifiers, a privacy manifest, and the required
24+
microphone, speech, and encryption declarations.
25+
26+
### Security
27+
28+
- Mobile passwords are used only for the sign-in request and are never saved.
29+
Sessions are kept in the device-only iOS Keychain or encrypted with an
30+
Android Keystore-held key; Disconnect erases the session and server address.
31+
- The packaged local web client connects only to HTTPS servers, disables
32+
native WebView debugging and release logging, rejects cleartext traffic and
33+
Android backups, and limits server CORS to the two packaged Capacitor origins.
34+
35+
### Changed
36+
37+
- Authenticated API calls, files, avatars, workspace photos, terminals, app
38+
events, external links, and provider OAuth flows now resolve correctly
39+
through a selected mobile server while bundled brand and resident character
40+
assets remain local.
41+
1042
## [0.0.12] - 2026-07-26
1143

1244
### Fixed
@@ -348,7 +380,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
348380
notarization, stapled tickets, Gatekeeper verification, persistent
349381
Application Support, and isolated Apple container machines.
350382

351-
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.12...HEAD
383+
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.13...HEAD
384+
[0.0.13]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.13
352385
[0.0.12]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.12
353386
[0.0.11]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.11
354387
[0.0.10]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.10

README.md

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
&nbsp;·&nbsp;
1919
<a href="https://1helm.com/manual/install-linux"><strong>for Linux</strong></a>
2020
&nbsp;·&nbsp;
21+
<a href="https://github.com/gitcommit90/1Helm/releases/latest"><strong>for Android</strong></a>
22+
&nbsp;·&nbsp;
2123
<a href="https://1helm.com">The story</a>
2224
&nbsp;·&nbsp;
2325
<a href="https://1helm.com/manual">Ship's manual</a>
@@ -32,7 +34,7 @@
3234
<code>Self-hosted</code>&nbsp;&nbsp;
3335
<code>Model-agnostic</code>&nbsp;&nbsp;
3436
<code>Signed + notarized</code>&nbsp;&nbsp;
35-
<code>macOS · Windows · Linux</code>
37+
<code>macOS · Windows · Linux · iOS · Android</code>
3638
</p>
3739

3840
---
@@ -132,6 +134,26 @@ digest-verified, health-checked updater — see the
132134
it works best on a dedicated machine: your crew works around the clock, and
133135
your everyday computer takes naps.
134136

137+
### Connect from iPhone, iPad, or Android
138+
139+
The mobile apps are thin, native gateways to a 1Helm you already run. Install
140+
and finish setup on a supported Mac, Windows, or Linux host first, give that
141+
host an HTTPS address, then enter the address, username, and password in the
142+
mobile app. The password is used only for sign-in and is never retained; the
143+
resulting session is stored in the iOS Keychain or encrypted with a key held by
144+
Android Keystore.
145+
146+
- iPhone and iPad use the App Store build under the stable bundle identifier
147+
`com.gitcommit90.onehelm.mobile`.
148+
- Android is distributed directly as `1Helm-<version>-universal.apk` on the
149+
matching GitHub Release. Android may ask you to allow installs from the app
150+
you used to open the file. Verify the release SHA-256 and install it; future
151+
releases signed by the same permanent 1Helm certificate install in place.
152+
- The native clients require HTTPS, do not contain or initialize the 1Helm
153+
server, and do not retain host data or provider credentials beyond the
154+
selected server address and secure session token. Use **Disconnect** in the
155+
profile menu to erase both from the device.
156+
135157
## Ready on day one. Specialized by day one hundred.
136158

137159
<p align="center">
@@ -223,6 +245,8 @@ and an audit trail. A prompt saying “use this service” is not a connector.
223245
- Host-owned updates: a signed native Mac updater plus an atomic,
224246
digest-verified Linux system service with health-check rollback.
225247
- Signed, Apple-notarized, stapled Apple Silicon DMG releases.
248+
- Native iPhone/iPad and directly distributed Android gateway clients for an
249+
already configured HTTPS 1Helm host.
226250

227251
### Platform truth
228252

@@ -231,10 +255,11 @@ and an audit trail. A prompt saying “use this service” is not a connector.
231255
| **Apple Silicon macOS 26** | Native desktop product and real isolated Linux computer per resident (Apple `container machine`, `home-mount=none`). |
232256
| **Linux / CI** | Supported headless systemd host with one unprivileged LXC per resident (subordinate UID/GID mapping, exact ownership checks); CI may select an explicit test backend. |
233257
| **Windows 11 x64** | Native desktop product with one private WSL 2 world per resident (Windows-drive mounts and interop disabled); the signed Setup executable ships with every release. |
258+
| **iPhone and iPad** | App Store gateway to an already configured HTTPS 1Helm host; sessions live in the device-only iOS Keychain. |
259+
| **Android 7+** | Directly distributed signed universal APK gateway; sessions are encrypted by a key held in Android Keystore. |
234260

235-
Not yet shipped: a native Linux desktop shell, mobile clients, a hosted
236-
control plane, rich Photon attachment fidelity, or blind execution of
237-
community skills.
261+
Not yet shipped: a native Linux desktop shell, a hosted control plane, rich
262+
Photon attachment fidelity, or blind execution of community skills.
238263

239264
## Run the source workspace
240265

@@ -257,7 +282,7 @@ A fresh data directory opens first-run setup. The source runtime defaults to
257282
| `PORT` | `8123` | HTTP/WebSocket control-plane port. |
258283
| `CTRL_DATA_DIR` | `./data` | Databases, routing state, uploads, and narrow workspace mirrors. |
259284
| `HELM_CHANNEL_COMPUTER_BACKEND` | `apple` on macOS, `lxc` on Linux, `wsl` on Windows | Host isolation backend; `native` and `mock` are explicit development/test overrides. |
260-
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.12` | Versioned channel-machine image contract. |
285+
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.13` | Versioned channel-machine image contract. |
261286

262287
### Agent-first JSON CLI
263288

android/.gitignore

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
2+
3+
# Built application files
4+
*.apk
5+
*.aar
6+
*.ap_
7+
*.aab
8+
9+
# Files for the ART/Dalvik VM
10+
*.dex
11+
12+
# Java class files
13+
*.class
14+
15+
# Generated files
16+
bin/
17+
gen/
18+
out/
19+
# Uncomment the following line in case you need and you don't have the release build type files in your app
20+
# release/
21+
22+
# Gradle files
23+
.gradle/
24+
build/
25+
26+
# Local configuration file (sdk path, etc)
27+
local.properties
28+
29+
# Proguard folder generated by Eclipse
30+
proguard/
31+
32+
# Log Files
33+
*.log
34+
35+
# Android Studio Navigation editor temp files
36+
.navigation/
37+
38+
# Android Studio captures folder
39+
captures/
40+
41+
# IntelliJ
42+
*.iml
43+
.idea/workspace.xml
44+
.idea/tasks.xml
45+
.idea/gradle.xml
46+
.idea/assetWizardSettings.xml
47+
.idea/dictionaries
48+
.idea/libraries
49+
# Android Studio 3 in .gitignore file.
50+
.idea/caches
51+
.idea/modules.xml
52+
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
53+
.idea/navEditor.xml
54+
55+
# Keystore files
56+
*.jks
57+
*.keystore
58+
*.properties.local
59+
60+
# External native build folder generated in Android Studio 2.2 and later
61+
.externalNativeBuild
62+
.cxx/
63+
64+
# Google Services (e.g. APIs or Firebase)
65+
# google-services.json
66+
67+
# Freeline
68+
freeline.py
69+
freeline/
70+
freeline_project_description.json
71+
72+
# fastlane
73+
fastlane/report.xml
74+
fastlane/Preview.html
75+
fastlane/screenshots
76+
fastlane/test_output
77+
fastlane/readme.md
78+
79+
# Version control
80+
vcs.xml
81+
82+
# lint
83+
lint/intermediates/
84+
lint/generated/
85+
lint/outputs/
86+
lint/tmp/
87+
# lint/reports/
88+
89+
# Android Profiling
90+
*.hprof
91+
92+
# Cordova plugins for Capacitor
93+
capacitor-cordova-android-plugins
94+
95+
# Copied web assets
96+
app/src/main/assets/public
97+
98+
# Generated Config files
99+
app/src/main/assets/capacitor.config.json
100+
app/src/main/assets/capacitor.plugins.json
101+
app/src/main/res/xml/config.xml

android/app/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/build/*
2+
!/build/.npmkeep

android/app/build.gradle

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import java.util.Properties
2+
3+
apply plugin: 'com.android.application'
4+
5+
def oneHelmVersion = new groovy.json.JsonSlurper().parseText(file('../../package.json').text).version as String
6+
def oneHelmVersionParts = oneHelmVersion.tokenize('.').collect { it as int }
7+
def oneHelmVersionCode = oneHelmVersionParts[0] * 1000000 + oneHelmVersionParts[1] * 1000 + oneHelmVersionParts[2]
8+
def signingPropertiesPath = System.getenv('HELM_ANDROID_SIGNING_PROPERTIES')
9+
def signingProperties = new Properties()
10+
if (signingPropertiesPath) {
11+
def signingPropertiesFile = file(signingPropertiesPath)
12+
if (!signingPropertiesFile.isFile()) throw new GradleException('HELM_ANDROID_SIGNING_PROPERTIES does not name a readable file.')
13+
signingPropertiesFile.withInputStream { signingProperties.load(it) }
14+
}
15+
16+
android {
17+
namespace = "com.gitcommit90.onehelm.mobile"
18+
compileSdk = rootProject.ext.compileSdkVersion
19+
defaultConfig {
20+
applicationId "com.gitcommit90.onehelm.mobile"
21+
minSdkVersion rootProject.ext.minSdkVersion
22+
targetSdkVersion rootProject.ext.targetSdkVersion
23+
versionCode oneHelmVersionCode
24+
versionName oneHelmVersion
25+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
26+
aaptOptions {
27+
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
28+
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
29+
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
30+
}
31+
}
32+
signingConfigs {
33+
release {
34+
if (signingPropertiesPath) {
35+
storeFile file(signingProperties.getProperty('storeFile'))
36+
storePassword signingProperties.getProperty('storePassword')
37+
keyAlias signingProperties.getProperty('keyAlias')
38+
keyPassword signingProperties.getProperty('keyPassword')
39+
enableV1Signing true
40+
enableV2Signing true
41+
enableV3Signing true
42+
enableV4Signing true
43+
}
44+
}
45+
}
46+
buildTypes {
47+
release {
48+
if (!signingPropertiesPath) throw new GradleException('Release builds require HELM_ANDROID_SIGNING_PROPERTIES.')
49+
signingConfig signingConfigs.release
50+
minifyEnabled true
51+
shrinkResources true
52+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
53+
}
54+
}
55+
}
56+
57+
repositories {
58+
flatDir{
59+
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
60+
}
61+
}
62+
63+
dependencies {
64+
implementation fileTree(include: ['*.jar'], dir: 'libs')
65+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
66+
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
67+
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
68+
implementation project(':capacitor-android')
69+
testImplementation "junit:junit:$junitVersion"
70+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
71+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
72+
implementation project(':capacitor-cordova-android-plugins')
73+
}
74+
75+
apply from: 'capacitor.build.gradle'
76+
77+
try {
78+
def servicesJSON = file('google-services.json')
79+
if (servicesJSON.text) {
80+
apply plugin: 'com.google.gms.google-services'
81+
}
82+
} catch(Exception e) {
83+
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
84+
}

android/app/capacitor.build.gradle

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
2+
3+
android {
4+
compileOptions {
5+
sourceCompatibility JavaVersion.VERSION_21
6+
targetCompatibility JavaVersion.VERSION_21
7+
}
8+
}
9+
10+
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
11+
dependencies {
12+
implementation project(':aparajita-capacitor-secure-storage')
13+
implementation project(':capacitor-app')
14+
implementation project(':capacitor-browser')
15+
implementation project(':capacitor-keyboard')
16+
implementation project(':capacitor-splash-screen')
17+
implementation project(':capacitor-status-bar')
18+
19+
}
20+
21+
22+
if (hasProperty('postBuildExtras')) {
23+
postBuildExtras()
24+
}

android/app/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.getcapacitor.myapp;
2+
3+
import static org.junit.Assert.*;
4+
5+
import android.content.Context;
6+
import androidx.test.ext.junit.runners.AndroidJUnit4;
7+
import androidx.test.platform.app.InstrumentationRegistry;
8+
import org.junit.Test;
9+
import org.junit.runner.RunWith;
10+
11+
/**
12+
* Instrumented test, which will execute on an Android device.
13+
*
14+
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
15+
*/
16+
@RunWith(AndroidJUnit4.class)
17+
public class ExampleInstrumentedTest {
18+
19+
@Test
20+
public void useAppContext() throws Exception {
21+
// Context of the app under test.
22+
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23+
24+
assertEquals("com.getcapacitor.app", appContext.getPackageName());
25+
}
26+
}

0 commit comments

Comments
 (0)