feat(ios): support building for both physical devices and simulators
- Replace hardcoded simulator framework path with SDK-conditional paths so Xcode automatically resolves iosSimulatorArm64 or iosArm64 based on the selected build target - Fix Compose Resources build script to use \$PLATFORM_NAME instead of \$CONFIGURATION for platform detection - Add CODE_SIGN_IDENTITY override for iphoneos SDK to enable device signing - Document simulator and physical device build workflows in README
This commit is contained in:
parent
dad37f510e
commit
86d84a350f
4 changed files with 89 additions and 10 deletions
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
|
@ -173,9 +173,12 @@ jobs:
|
|||
key: ${{ runner.os }}-konan-${{ hashFiles('gradle/libs.versions.toml') }}
|
||||
restore-keys: ${{ runner.os }}-konan-
|
||||
|
||||
- name: Build shared KMP framework
|
||||
- name: Build shared KMP framework (simulator)
|
||||
run: ./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
|
||||
|
||||
- name: Build shared KMP framework (device)
|
||||
run: ./gradlew :shared:linkDebugFrameworkIosArm64
|
||||
|
||||
- name: Build iOS app
|
||||
run: |
|
||||
xcodebuild -project iosApp/iosApp.xcodeproj \
|
||||
|
|
|
|||
38
README.md
38
README.md
|
|
@ -55,6 +55,30 @@ NON_BROWSER_VIOLATION_SCORE=0
|
|||
|
||||
Without this setting, the server's violation system may accumulate ban points against the mobile client if the User-Agent check fails, eventually locking the account out.
|
||||
|
||||
### Self-Signed Certificates (iOS)
|
||||
|
||||
The iOS app can connect to a LibreChat server using a self-signed certificate, but iOS enforces strict requirements on all TLS certificates regardless of whether they are self-signed or CA-issued. The certificate must meet **all** of the following or iOS will reject the connection:
|
||||
|
||||
| Requirement | Details |
|
||||
|---|---|
|
||||
| **Validity period** | ≤ 398 days (for certificates issued after September 1, 2020) |
|
||||
| **Subject Alternative Name** | DNS name must be present in the SAN extension — CommonName alone is not accepted |
|
||||
| **Key algorithm** | RSA ≥ 2048 bits |
|
||||
| **Signature algorithm** | SHA-256 or better (SHA-1 is not accepted) |
|
||||
| **Extended Key Usage** | Must include `id-kp-serverAuth` (TLS Web Server Authentication) |
|
||||
|
||||
The 398-day validity limit is the most common cause of failures — a certificate that works in every browser but was generated with a multi-year validity will be rejected by iOS.
|
||||
|
||||
**Setup steps:**
|
||||
|
||||
1. Generate a compliant certificate signed by your own CA (tools like [`mkcert`](https://github.com/FiloSottile/mkcert) handle all requirements automatically)
|
||||
2. On your iPhone: **Settings → General → VPN & Device Management** — install your CA certificate
|
||||
3. Go to **Settings → General → About → Certificate Trust Settings** — enable full trust for your CA
|
||||
|
||||
Once the CA is trusted on the device, you only need to re-install it if you create a new CA. The server certificate itself (which you replace when it expires) does not need to be installed separately.
|
||||
|
||||
> **Android** has no equivalent restrictions — self-signed certificates with any validity period work as long as the CA is trusted on the device.
|
||||
|
||||
### Notes
|
||||
|
||||
- **Registration** — The app respects your server's registration settings. If registration is disabled server-side, only the login form is shown.
|
||||
|
|
@ -88,16 +112,26 @@ For a release build:
|
|||
|
||||
### iOS
|
||||
|
||||
Build and run on the iOS Simulator:
|
||||
**Simulator:**
|
||||
|
||||
```bash
|
||||
./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
|
||||
xcodebuild -project iosApp/iosApp.xcodeproj -scheme iosApp \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 16' \
|
||||
-derivedDataPath iosApp/build build
|
||||
```
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for full simulator install and launch commands.
|
||||
**Physical device:**
|
||||
|
||||
```bash
|
||||
./gradlew :shared:linkDebugFrameworkIosArm64
|
||||
open iosApp/iosApp.xcodeproj
|
||||
```
|
||||
|
||||
Then in Xcode: select your device, set your Team under Signing & Capabilities, and press ⌘R.
|
||||
|
||||
See [iosApp/README.md](iosApp/README.md) for full build and launch instructions.
|
||||
|
||||
### Tech Stack
|
||||
|
||||
|
|
|
|||
|
|
@ -35,25 +35,51 @@ The `:shared` Gradle module exports `core:common`, `core:model`, `core:network`,
|
|||
|
||||
### Build and Run
|
||||
|
||||
#### Simulator
|
||||
|
||||
```bash
|
||||
# Build the app (Xcode build phases handle the shared framework automatically)
|
||||
# 1. Build the shared KMP framework
|
||||
./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
|
||||
|
||||
# 2. Build the Xcode project
|
||||
xcodebuild -project iosApp/iosApp.xcodeproj -scheme iosApp \
|
||||
-sdk iphonesimulator \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 16' \
|
||||
-derivedDataPath iosApp/build build
|
||||
|
||||
# Boot simulator, install, and launch
|
||||
# 3. Boot simulator, install, and launch
|
||||
xcrun simctl boot "iPhone 16"
|
||||
xcrun simctl install booted iosApp/build/Build/Products/Debug-iphonesimulator/iosApp.app
|
||||
xcrun simctl launch booted com.garfiec.librechat.ios
|
||||
```
|
||||
|
||||
> **Note:** The first build takes several minutes while the Kotlin/Native toolchain downloads.
|
||||
|
||||
To rebuild only the shared KMP framework (e.g., after changing shared Kotlin code):
|
||||
#### Physical Device
|
||||
|
||||
```bash
|
||||
./gradlew :shared:linkDebugFrameworkIosSimulatorArm64
|
||||
# 1. Build the shared KMP framework for device
|
||||
./gradlew :shared:linkDebugFrameworkIosArm64
|
||||
```
|
||||
|
||||
Then open the project in Xcode:
|
||||
|
||||
```bash
|
||||
open iosApp/iosApp.xcodeproj
|
||||
```
|
||||
|
||||
In Xcode:
|
||||
1. Select your iPhone from the device picker (top toolbar)
|
||||
2. Go to **Signing & Capabilities** → set your **Team** (required for device signing)
|
||||
3. Press **⌘R** to build and install
|
||||
|
||||
> **Note:** The first build takes several minutes while the Kotlin/Native toolchain downloads. You need an Apple Developer account (free tier works) for device signing.
|
||||
|
||||
### Known Xcode Behaviour
|
||||
|
||||
**Red dot on `Shared.framework` in the navigator** — You may see a red indicator on `Shared.framework` in the Xcode file navigator (left sidebar). This is cosmetic and does not affect builds. It happens because the Xcode project file keeps a static reference path to the framework for display purposes, and that path may not exist if you haven't built that particular target yet. The linker always resolves the framework via `FRAMEWORK_SEARCH_PATHS`, which is set correctly per target — the red dot can be safely ignored. Building the Gradle framework for your active target clears it:
|
||||
|
||||
```bash
|
||||
./gradlew :shared:linkDebugFrameworkIosSimulatorArm64 # clears it for simulator
|
||||
./gradlew :shared:linkDebugFrameworkIosArm64 # clears it for device
|
||||
```
|
||||
|
||||
## Info.plist Permissions
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Aggregate and copy Compose Multiplatform resources into the app bundle.\n# Runs the Gradle aggregation task to ensure resources from all feature\n# modules are collected, then copies them into the .app bundle where\n# the Compose resource reader expects them at runtime.\n\nif [ \"$CONFIGURATION\" = \"Debug\" ]; then\n ARCH_DIR=\"iosSimulatorArm64\"\n GRADLE_TASK=\":shared:iosSimulatorArm64AggregateResources\"\nelse\n ARCH_DIR=\"iosArm64\"\n GRADLE_TASK=\":shared:iosArm64AggregateResources\"\nfi\n\nPROJECT_ROOT=\"$SRCROOT/..\"\nSRC=\"$PROJECT_ROOT/shared/build/kotlin-multiplatform-resources/aggregated-resources/$ARCH_DIR/composeResources\"\nDST=\"$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app/compose-resources/composeResources\"\n\n# Run Gradle aggregation to ensure resources are up to date\necho \"Running $GRADLE_TASK...\"\ncd \"$PROJECT_ROOT\" && ./gradlew \"$GRADLE_TASK\" 2>&1\nGRADLE_EXIT=$?\nif [ $GRADLE_EXIT -ne 0 ]; then\n echo \"error: Gradle task $GRADLE_TASK failed with exit code $GRADLE_EXIT\"\n exit $GRADLE_EXIT\nfi\n\n# Clean stale resources before copying fresh ones\nif [ -d \"$DST\" ]; then\n rm -rf \"$DST\"\nfi\n\nif [ -d \"$SRC\" ]; then\n mkdir -p \"$DST\"\n cp -R \"$SRC/\" \"$DST/\"\n echo \"Copied Compose resources from $SRC to $DST\"\nelse\n echo \"error: Compose resources not found at $SRC after Gradle aggregation\"\n exit 1\nfi\n";
|
||||
shellScript = "# Aggregate and copy Compose Multiplatform resources into the app bundle.\n# Runs the Gradle aggregation task to ensure resources from all feature\n# modules are collected, then copies them into the .app bundle where\n# the Compose resource reader expects them at runtime.\n\nif [ \"$PLATFORM_NAME\" = \"iphonesimulator\" ]; then\n ARCH_DIR=\"iosSimulatorArm64\"\n GRADLE_TASK=\":shared:iosSimulatorArm64AggregateResources\"\nelif [ \"$PLATFORM_NAME\" = \"iphoneos\" ]; then\n ARCH_DIR=\"iosArm64\"\n GRADLE_TASK=\":shared:iosArm64AggregateResources\"\nelse\n echo \"error: Unsupported PLATFORM_NAME: $PLATFORM_NAME\"\n exit 1\nfi\n\nPROJECT_ROOT=\"$SRCROOT/..\"\nSRC=\"$PROJECT_ROOT/shared/build/kotlin-multiplatform-resources/aggregated-resources/$ARCH_DIR/composeResources\"\nDST=\"$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app/compose-resources/composeResources\"\n\n# Run Gradle aggregation to ensure resources are up to date\necho \"Running $GRADLE_TASK...\"\ncd \"$PROJECT_ROOT\" && ./gradlew \"$GRADLE_TASK\" 2>&1\nGRADLE_EXIT=$?\nif [ $GRADLE_EXIT -ne 0 ]; then\n echo \"error: Gradle task $GRADLE_TASK failed with exit code $GRADLE_EXIT\"\n exit $GRADLE_EXIT\nfi\n\n# Clean stale resources before copying fresh ones\nif [ -d \"$DST\" ]; then\n rm -rf \"$DST\"\nfi\n\nif [ -d \"$SRC\" ]; then\n mkdir -p \"$DST\"\n cp -R \"$SRC/\" \"$DST/\"\n echo \"Copied Compose resources from $SRC to $DST\"\nelse\n echo \"error: Compose resources not found at $SRC after Gradle aggregation\"\n exit 1\nfi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
|
|
@ -237,14 +237,22 @@
|
|||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
);
|
||||
"FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/../shared/build/bin/iosSimulatorArm64/debugFramework",
|
||||
);
|
||||
"FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/../shared/build/bin/iosArm64/debugFramework",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = iosApp/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
|
@ -268,12 +276,20 @@
|
|||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
);
|
||||
"FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/../shared/build/bin/iosSimulatorArm64/releaseFramework",
|
||||
);
|
||||
"FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/../shared/build/bin/iosArm64/releaseFramework",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
|
|
|
|||
Loading…
Reference in a new issue