diff --git a/.gitignore b/.gitignore index ea7f13ee132..162f2fb6ca3 100644 --- a/.gitignore +++ b/.gitignore @@ -36,10 +36,12 @@ bin/docs-list apps/macos/.build-local/ apps/macos/.swiftpm/ apps/shared/MoltbotKit/.swiftpm/ +apps/shared/OpenClawKit/.swiftpm/ Core/ apps/ios/*.xcodeproj/ apps/ios/*.xcworkspace/ apps/ios/.swiftpm/ +apps/ios/.local-signing.xcconfig vendor/ apps/ios/Clawdbot.xcodeproj/ apps/ios/Clawdbot.xcodeproj/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b4b3f53611..6148c92d5cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai - iOS/Gateway: stabilize connect/discovery state handling, add onboarding reset recovery in Settings, and fix iOS gateway-controller coverage for command-surface and last-connection persistence behavior. (#18164) Thanks @mbelinky. - iOS/Talk: harden mobile talk config handling by ignoring redacted/env-placeholder API keys, support secure local keychain override, improve accessibility motion/contrast behavior in status UI, and tighten ATS to local-network allowance. (#18163) Thanks @mbelinky. - iOS/Location: restore the significant location monitor implementation (service hooks + protocol surface + ATS key alignment) after merge drift so iOS builds compile again. (#18260) Thanks @ngutman. +- iOS/Signing: auto-select local Apple Development team during iOS project generation/build, prefer the canonical OpenClaw team when available, and support local per-machine signing overrides without committing team IDs. (#18421) Thanks @ngutman. - Discord/Telegram: make per-account message action gates effective for both action listing and execution, and preserve top-level gate restrictions when account overrides only specify a subset of `actions` keys (account key -> base key -> default fallback). (#18494) - Telegram: keep DM-topic replies and draft previews in the originating private-chat topic by preserving positive `message_thread_id` values for DM threads. (#18586) Thanks @sebslight. - Telegram: preserve private-chat topic `message_thread_id` on outbound sends (message/sticker/poll), keep thread-not-found retry fallback, and avoid masking `chat not found` routing errors. (#18993) Thanks @obviyus. diff --git a/apps/ios/Config/Signing.xcconfig b/apps/ios/Config/Signing.xcconfig new file mode 100644 index 00000000000..e3d58571439 --- /dev/null +++ b/apps/ios/Config/Signing.xcconfig @@ -0,0 +1,13 @@ +// Shared iOS signing defaults for local development + CI. +OPENCLAW_IOS_DEFAULT_TEAM = Y5PE65HELJ +OPENCLAW_IOS_SELECTED_TEAM = $(OPENCLAW_IOS_DEFAULT_TEAM) + +// Local contributors can override this by running scripts/ios-configure-signing.sh. +#include? "../.local-signing.xcconfig" + +CODE_SIGN_STYLE = Automatic +CODE_SIGN_IDENTITY = Apple Development +DEVELOPMENT_TEAM = $(OPENCLAW_IOS_SELECTED_TEAM) + +// Let Xcode manage provisioning for the selected local team. +PROVISIONING_PROFILE_SPECIFIER = diff --git a/apps/ios/README.md b/apps/ios/README.md index 2e426c18d70..f7fe696d86b 100644 --- a/apps/ios/README.md +++ b/apps/ios/README.md @@ -39,13 +39,20 @@ pnpm install pnpm ios:open ``` +`pnpm ios:open` now runs `scripts/ios-configure-signing.sh` before `xcodegen`: + +- If `IOS_DEVELOPMENT_TEAM` is set, it uses that team. +- Otherwise it prefers the canonical OpenClaw team (`Y5PE65HELJ`) when that team exists locally. +- If not present, it picks the first non-personal team from your Xcode account (falls back to personal team if needed). +- It writes the selected team to `apps/ios/.local-signing.xcconfig` (local-only, gitignored). + Then in Xcode: 1. Select the `OpenClaw` scheme 2. Select a simulator or a connected device 3. Run -If you're using a personal Apple Development team, you may need to change the bundle identifier in Xcode to a unique value so signing succeeds. +If you're using a personal Apple Development team, you may still need to change the bundle identifier in Xcode to a unique value so signing succeeds. ## Build From CLI diff --git a/apps/ios/fastlane/Fastfile b/apps/ios/fastlane/Fastfile index b777c25c7a5..f1dbf6df18c 100644 --- a/apps/ios/fastlane/Fastfile +++ b/apps/ios/fastlane/Fastfile @@ -66,7 +66,8 @@ platform :ios do if team_id.nil? || team_id.strip.empty? helper_path = File.expand_path("../../scripts/ios-team-id.sh", __dir__) if File.exist?(helper_path) - team_id = sh("bash #{helper_path.shellescape}").strip + # Keep CI/local compatibility where teams are present in keychain but not Xcode account metadata. + team_id = sh("IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK=1 bash #{helper_path.shellescape}").strip end end UI.user_error!("Missing IOS_DEVELOPMENT_TEAM (Apple Team ID). Add it to fastlane/.env or export it in your shell.") if team_id.nil? || team_id.strip.empty? diff --git a/apps/ios/fastlane/SETUP.md b/apps/ios/fastlane/SETUP.md index 832f1ebc15b..930258fcc79 100644 --- a/apps/ios/fastlane/SETUP.md +++ b/apps/ios/fastlane/SETUP.md @@ -22,7 +22,7 @@ ASC_KEY_PATH=/absolute/path/to/AuthKey_XXXXXXXXXX.p8 IOS_DEVELOPMENT_TEAM=YOUR_TEAM_ID ``` -Tip: run `scripts/ios-team-id.sh` from the repo root to print a Team ID to paste into `.env`. Fastlane falls back to this helper if `IOS_DEVELOPMENT_TEAM` is missing. +Tip: run `scripts/ios-team-id.sh` from the repo root to print a Team ID to paste into `.env`. The helper prefers the canonical OpenClaw team (`Y5PE65HELJ`) when present locally; otherwise it prefers the first non-personal team from your Xcode account (then personal team if needed). Fastlane uses this helper automatically if `IOS_DEVELOPMENT_TEAM` is missing. Run: diff --git a/apps/ios/project.yml b/apps/ios/project.yml index 4231172b777..8196c0f437f 100644 --- a/apps/ios/project.yml +++ b/apps/ios/project.yml @@ -66,13 +66,12 @@ targets: exit 1 fi swiftlint lint --config "$SRCROOT/.swiftlint.yml" --use-script-input-file-lists + configFiles: + Debug: Config/Signing.xcconfig + Release: Config/Signing.xcconfig settings: base: - CODE_SIGN_IDENTITY: "Apple Development" - CODE_SIGN_STYLE: Manual - DEVELOPMENT_TEAM: Y5PE65HELJ PRODUCT_BUNDLE_IDENTIFIER: ai.openclaw.ios - PROVISIONING_PROFILE_SPECIFIER: "ai.openclaw.ios Development" SWIFT_VERSION: "6.0" SWIFT_STRICT_CONCURRENCY: complete ENABLE_APPINTENTS_METADATA: NO diff --git a/package.json b/package.json index c26cb2ccfba..9272d7bdfa5 100644 --- a/package.json +++ b/package.json @@ -71,10 +71,10 @@ "gateway:dev": "OPENCLAW_SKIP_CHANNELS=1 CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway", "gateway:dev:reset": "OPENCLAW_SKIP_CHANNELS=1 CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway --reset", "gateway:watch": "node scripts/watch-node.mjs gateway --force", - "ios:build": "bash -lc 'cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build'", - "ios:gen": "cd apps/ios && xcodegen generate", - "ios:open": "cd apps/ios && xcodegen generate && open OpenClaw.xcodeproj", - "ios:run": "bash -lc 'cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build && xcrun simctl boot \"${IOS_SIM:-iPhone 17}\" || true && xcrun simctl launch booted ai.openclaw.ios'", + "ios:build": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build'", + "ios:gen": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate'", + "ios:open": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate && open OpenClaw.xcodeproj'", + "ios:run": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build && xcrun simctl boot \"${IOS_SIM:-iPhone 17}\" || true && xcrun simctl launch booted ai.openclaw.ios'", "lint": "oxlint --type-aware", "lint:all": "pnpm lint && pnpm lint:swift", "lint:docs": "pnpm dlx markdownlint-cli2", diff --git a/scripts/ios-configure-signing.sh b/scripts/ios-configure-signing.sh new file mode 100755 index 00000000000..f7fceb72227 --- /dev/null +++ b/scripts/ios-configure-signing.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +IOS_DIR="${ROOT_DIR}/apps/ios" +TEAM_ID_SCRIPT="${ROOT_DIR}/scripts/ios-team-id.sh" +LOCAL_SIGNING_FILE="${IOS_DIR}/.local-signing.xcconfig" + +if [[ ! -x "${TEAM_ID_SCRIPT}" ]]; then + echo "ERROR: Missing team detection helper: ${TEAM_ID_SCRIPT}" >&2 + exit 1 +fi + +team_id="" +if team_id="$("${TEAM_ID_SCRIPT}" 2>/dev/null)"; then + : +else + if [[ "${IOS_SIGNING_REQUIRED:-0}" == "1" ]]; then + "${TEAM_ID_SCRIPT}" + exit 1 + fi + + echo "WARN: Unable to detect an Apple Team ID; keeping existing iOS signing override (if any)." >&2 + exit 0 +fi + +tmp_file="$(mktemp "${TMPDIR:-/tmp}/openclaw-ios-signing.XXXXXX")" +cat >"${tmp_file}" <&2 +preferred_team="${IOS_PREFERRED_TEAM_ID:-${OPENCLAW_IOS_DEFAULT_TEAM_ID:-Y5PE65HELJ}}" +preferred_team_name="${IOS_PREFERRED_TEAM_NAME:-}" +allow_keychain_fallback="${IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK:-0}" +prefer_non_free_team="${IOS_PREFER_NON_FREE_TEAM:-1}" + +declare -a team_ids=() +declare -a team_is_free=() +declare -a team_names=() + +append_team() { + local candidate_id="$1" + local candidate_is_free="$2" + local candidate_name="$3" + [[ -z "$candidate_id" ]] && return + + local i + for i in "${!team_ids[@]}"; do + if [[ "${team_ids[$i]}" == "$candidate_id" ]]; then + return + fi + done + + team_ids+=("$candidate_id") + team_is_free+=("$candidate_is_free") + team_names+=("$candidate_name") +} + +load_teams_from_xcode_preferences() { + local plist_path="${HOME}/Library/Preferences/com.apple.dt.Xcode.plist" + [[ -f "$plist_path" ]] || return 0 + + while IFS=$'\t' read -r team_id is_free team_name; do + [[ -z "$team_id" ]] && continue + append_team "$team_id" "${is_free:-0}" "${team_name:-}" + done < <( + plutil -extract IDEProvisioningTeams json -o - "$plist_path" 2>/dev/null \ + | /usr/bin/python3 -c ' +import json +import sys + +try: + data = json.load(sys.stdin) +except Exception: + raise SystemExit(0) + +if not isinstance(data, dict): + raise SystemExit(0) + +for teams in data.values(): + if not isinstance(teams, list): + continue + for team in teams: + if not isinstance(team, dict): + continue + team_id = str(team.get("teamID", "")).strip() + if not team_id: + continue + is_free = "1" if bool(team.get("isFreeProvisioningTeam", False)) else "0" + team_name = str(team.get("teamName", "")).replace("\t", " ").strip() + print(f"{team_id}\t{is_free}\t{team_name}") +' + ) +} + +load_teams_from_legacy_defaults_key() { + while IFS= read -r team; do + [[ -z "$team" ]] && continue + append_team "$team" "0" "" + done < <( + defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers 2>/dev/null \ + | grep -Eo '[A-Z0-9]{10}' || true + ) +} + +load_teams_from_xcode_preferences +load_teams_from_legacy_defaults_key + +if [[ ${#team_ids[@]} -eq 0 && "$allow_keychain_fallback" == "1" ]]; then + while IFS= read -r team; do + [[ -z "$team" ]] && continue + append_team "$team" "0" "" + done < <( + security find-identity -p codesigning -v 2>/dev/null \ + | grep -Eo '\([A-Z0-9]{10}\)' \ + | tr -d '()' || true + ) +fi + +if [[ ${#team_ids[@]} -eq 0 ]]; then + if [[ "$allow_keychain_fallback" == "1" ]]; then + echo "No Apple Team ID found. Open Xcode or install signing certificates first." >&2 + else + echo "No Apple Team ID found in Xcode accounts. Open Xcode → Settings → Accounts and sign in, then retry." >&2 + echo "(Set IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK=1 to allow keychain-only team detection.)" >&2 + fi exit 1 fi -echo "$team_id" +for i in "${!team_ids[@]}"; do + if [[ "${team_ids[$i]}" == "$preferred_team" ]]; then + printf '%s\n' "${team_ids[$i]}" + exit 0 + fi +done + +if [[ -n "$preferred_team_name" ]]; then + preferred_team_name_lc="$(printf '%s' "$preferred_team_name" | tr '[:upper:]' '[:lower:]')" + for i in "${!team_ids[@]}"; do + team_name_lc="$(printf '%s' "${team_names[$i]}" | tr '[:upper:]' '[:lower:]')" + if [[ "$team_name_lc" == "$preferred_team_name_lc" ]]; then + printf '%s\n' "${team_ids[$i]}" + exit 0 + fi + done +fi + +if [[ "$prefer_non_free_team" == "1" ]]; then + for i in "${!team_ids[@]}"; do + if [[ "${team_is_free[$i]}" == "0" ]]; then + printf '%s\n' "${team_ids[$i]}" + exit 0 + fi + done +fi + +printf '%s\n' "${team_ids[0]}"