mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-24 10:58:37 +00:00
feat: wire ios push sandbox tooling
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
"platform": "IOS",
|
||||
"profileKey": "OPENCLAW_APP_PROFILE",
|
||||
"profileName": "OpenClaw App Store ai.openclawfoundation.app",
|
||||
"capabilities": ["PUSH_NOTIFICATIONS", "APP_GROUPS"],
|
||||
"capabilities": ["PUSH_NOTIFICATIONS", "APP_GROUPS", "APP_ATTEST"],
|
||||
"appGroups": ["group.ai.openclawfoundation.app.shared"]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ OPENCLAW_IOS_SELECTED_TEAM = $(OPENCLAW_IOS_DEFAULT_TEAM)
|
||||
OPENCLAW_DEVELOPMENT_TEAM = $(OPENCLAW_IOS_SELECTED_TEAM)
|
||||
OPENCLAW_CODE_SIGN_STYLE = Automatic
|
||||
OPENCLAW_CODE_SIGN_IDENTITY = Apple Development
|
||||
OPENCLAW_CODE_SIGN_ENTITLEMENTS = Sources/OpenClaw.entitlements
|
||||
OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app
|
||||
OPENCLAW_APP_GROUP_ID = group.ai.openclawfoundation.app.shared
|
||||
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
OPENCLAW_CODE_SIGN_STYLE = Manual
|
||||
OPENCLAW_CODE_SIGN_IDENTITY = Apple Development
|
||||
OPENCLAW_CODE_SIGN_ENTITLEMENTS = Sources/OpenClaw.entitlements
|
||||
OPENCLAW_DEVELOPMENT_TEAM = FWJYW4S8P8
|
||||
|
||||
OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app
|
||||
|
||||
@@ -495,6 +495,9 @@ def produce_services_for_target(target)
|
||||
if target.fetch("capabilities").include?("APP_GROUPS")
|
||||
services[:app_group] = "on"
|
||||
end
|
||||
if target.fetch("capabilities").include?("APP_ATTEST")
|
||||
services[:app_attest] = "on"
|
||||
end
|
||||
services
|
||||
end
|
||||
|
||||
@@ -605,6 +608,15 @@ def validate_match_profile_capabilities!(target)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if capabilities.include?("APP_ATTEST")
|
||||
app_attest_environments = profile_plist_array_values(profile_path, "Entitlements:com.apple.developer.devicecheck.appattest-environment")
|
||||
unless app_attest_environments.include?("production")
|
||||
UI.user_error!(
|
||||
"Provisioning profile #{target.fetch("profileName")} for #{target.fetch("bundleId")} is missing production App Attest entitlement; actual environments: #{app_attest_environments.empty? ? "missing" : app_attest_environments.join(", ")}."
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sync_app_store_signing!(readonly:)
|
||||
|
||||
@@ -65,7 +65,7 @@ pnpm ios:release:signing:check
|
||||
pnpm ios:release:signing:setup
|
||||
```
|
||||
|
||||
`signing:setup` uses Fastlane `produce` and `modify_services` to create Developer Portal bundle IDs and enable required services before running `match`. The main app and share extension also require the shared App Group from `apps/ios/Config/AppStoreSigning.json`; associate that group with both bundle IDs in the Apple Developer Portal before regenerating profiles. If Fastlane does not already have a valid Apple Developer Portal session, run `fastlane spaceauth` for a release-owner Apple ID and export the resulting `FASTLANE_SESSION`.
|
||||
`signing:setup` uses Fastlane `produce` and `modify_services` to create Developer Portal bundle IDs and enable required services before running `match`. The main app also requires App Attest, and the main app and share extension both require the shared App Group from `apps/ios/Config/AppStoreSigning.json`; associate that group with both bundle IDs in the Apple Developer Portal before regenerating profiles. If Fastlane does not already have a valid Apple Developer Portal session, run `fastlane spaceauth` for a release-owner Apple ID and export the resulting `FASTLANE_SESSION`.
|
||||
|
||||
Shared encrypted signing storage:
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ cat >"${tmp_file}" <<EOF
|
||||
// OPENCLAW_IOS_WATCH_APP_PROFILE
|
||||
OPENCLAW_CODE_SIGN_STYLE = ${code_sign_style}
|
||||
OPENCLAW_CODE_SIGN_IDENTITY = ${code_sign_identity}
|
||||
OPENCLAW_CODE_SIGN_ENTITLEMENTS = Sources/OpenClaw.entitlements
|
||||
OPENCLAW_DEVELOPMENT_TEAM = ${team_id}
|
||||
// Keep legacy key for compatibility with older signing config paths.
|
||||
OPENCLAW_IOS_SELECTED_TEAM = ${team_id}
|
||||
|
||||
@@ -185,12 +185,16 @@ OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app
|
||||
OPENCLAW_SHARE_BUNDLE_ID = ai.openclawfoundation.app.share
|
||||
OPENCLAW_ACTIVITY_WIDGET_BUNDLE_ID = ai.openclawfoundation.app.activitywidget
|
||||
OPENCLAW_WATCH_APP_BUNDLE_ID = ai.openclawfoundation.app.watchkitapp
|
||||
OPENCLAW_CODE_SIGN_ENTITLEMENTS = Sources/OpenClawAppAttest.entitlements
|
||||
OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT = production
|
||||
OPENCLAW_APP_ATTEST_ENVIRONMENT = production
|
||||
OPENCLAW_PUSH_TRANSPORT = relay
|
||||
OPENCLAW_PUSH_DISTRIBUTION = official
|
||||
OPENCLAW_URL_SLASH = /
|
||||
OPENCLAW_PUSH_RELAY_BASE_URL = ${PUSH_RELAY_BASE_URL_XCCONFIG}
|
||||
OPENCLAW_PUSH_APNS_ENVIRONMENT = production
|
||||
OPENCLAW_PUSH_RELAY_PROFILE = production
|
||||
OPENCLAW_PUSH_PROOF_POLICY = appleStrict
|
||||
EOF
|
||||
|
||||
(
|
||||
|
||||
@@ -15,23 +15,126 @@ XCODEGEN_BIN="${IOS_RUN_XCODEGEN_BIN:-xcodegen}"
|
||||
SIMCTL_BIN="${IOS_RUN_SIMCTL_BIN:-xcrun simctl}"
|
||||
PLIST_BUDDY_BIN="${IOS_RUN_PLIST_BUDDY_BIN:-/usr/libexec/PlistBuddy}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: scripts/ios-run.sh [options]
|
||||
|
||||
Options:
|
||||
--push-sandbox-simulator
|
||||
Build with the hosted sandbox push relay and launch the simulator with an
|
||||
internal simulator proof secret.
|
||||
--push-relay-base-url <url>
|
||||
Override the sandbox relay URL used with --push-sandbox-simulator.
|
||||
Defaults to https://ios-push-relay-sandbox.openclaw.ai.
|
||||
--simulator-proof-secret-env <name>
|
||||
Environment variable that contains the simulator proof secret.
|
||||
Defaults to OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET.
|
||||
-h, --help
|
||||
Show this help.
|
||||
EOF
|
||||
}
|
||||
|
||||
run_simctl() {
|
||||
# shellcheck disable=SC2086
|
||||
${SIMCTL_BIN} "$@"
|
||||
}
|
||||
|
||||
push_sandbox_simulator=0
|
||||
push_relay_base_url="${OPENCLAW_PUSH_SANDBOX_RELAY_BASE_URL:-https://ios-push-relay-sandbox.openclaw.ai}"
|
||||
simulator_proof_secret_env="${OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET_ENV:-OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--push-sandbox-simulator)
|
||||
push_sandbox_simulator=1
|
||||
;;
|
||||
--push-relay-base-url)
|
||||
if [[ $# -lt 2 || -z "$2" ]]; then
|
||||
echo "ERROR: --push-relay-base-url requires a URL" >&2
|
||||
exit 1
|
||||
fi
|
||||
push_relay_base_url="$2"
|
||||
shift
|
||||
;;
|
||||
--simulator-proof-secret-env)
|
||||
if [[ $# -lt 2 || -z "$2" ]]; then
|
||||
echo "ERROR: --simulator-proof-secret-env requires an environment variable name" >&2
|
||||
exit 1
|
||||
fi
|
||||
simulator_proof_secret_env="$2"
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
xcodebuild_overrides=()
|
||||
simulator_proof_secret=""
|
||||
|
||||
if [[ ! "${simulator_proof_secret_env}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
|
||||
echo "ERROR: Invalid simulator proof secret environment variable name" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${push_sandbox_simulator}" == "1" ]]; then
|
||||
simulator_proof_secret="${!simulator_proof_secret_env:-}"
|
||||
if [[ -z "${simulator_proof_secret}" ]]; then
|
||||
echo "ERROR: ${simulator_proof_secret_env} must be set for --push-sandbox-simulator" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${#simulator_proof_secret}" -lt 32 ]]; then
|
||||
echo "ERROR: ${simulator_proof_secret_env} must contain at least 32 characters" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
xcodebuild_overrides+=(
|
||||
"OPENCLAW_PUSH_TRANSPORT=relay"
|
||||
"OPENCLAW_PUSH_DISTRIBUTION=official"
|
||||
"OPENCLAW_PUSH_RELAY_BASE_URL=${push_relay_base_url}"
|
||||
"OPENCLAW_PUSH_APNS_ENVIRONMENT=sandbox"
|
||||
"OPENCLAW_PUSH_RELAY_PROFILE=simulatorSandbox"
|
||||
"OPENCLAW_PUSH_PROOF_POLICY=internalSimulator"
|
||||
"OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT=development"
|
||||
)
|
||||
fi
|
||||
|
||||
unset "${simulator_proof_secret_env}"
|
||||
if [[ "${simulator_proof_secret_env}" != "OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET" ]]; then
|
||||
unset OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET
|
||||
fi
|
||||
|
||||
"${ROOT_DIR}/scripts/ios-configure-signing.sh"
|
||||
"${ROOT_DIR}/scripts/ios-write-version-xcconfig.sh"
|
||||
|
||||
cd "${IOS_DIR}"
|
||||
"${XCODEGEN_BIN}" generate
|
||||
"${XCODEBUILD_BIN}" \
|
||||
-project OpenClaw.xcodeproj \
|
||||
-scheme OpenClaw \
|
||||
-destination "${IOS_DESTINATION}" \
|
||||
-configuration "${CONFIGURATION}" \
|
||||
-derivedDataPath "${DERIVED_DATA_DIR}" \
|
||||
build
|
||||
if [[ "${push_sandbox_simulator}" == "1" ]]; then
|
||||
"${XCODEBUILD_BIN}" \
|
||||
-project OpenClaw.xcodeproj \
|
||||
-scheme OpenClaw \
|
||||
-destination "${IOS_DESTINATION}" \
|
||||
-configuration "${CONFIGURATION}" \
|
||||
-derivedDataPath "${DERIVED_DATA_DIR}" \
|
||||
build \
|
||||
"${xcodebuild_overrides[@]}"
|
||||
else
|
||||
"${XCODEBUILD_BIN}" \
|
||||
-project OpenClaw.xcodeproj \
|
||||
-scheme OpenClaw \
|
||||
-destination "${IOS_DESTINATION}" \
|
||||
-configuration "${CONFIGURATION}" \
|
||||
-derivedDataPath "${DERIVED_DATA_DIR}" \
|
||||
build
|
||||
fi
|
||||
|
||||
app_path="${DERIVED_DATA_DIR}/Build/Products/${CONFIGURATION}-iphonesimulator/${APP_NAME}.app"
|
||||
if [[ ! -d "${app_path}" ]]; then
|
||||
@@ -54,4 +157,10 @@ if ! boot_output="$(run_simctl boot "${SIMULATOR_TARGET}" 2>&1)"; then
|
||||
fi
|
||||
|
||||
run_simctl install "${SIMULATOR_TARGET}" "${app_path}"
|
||||
run_simctl launch "${SIMULATOR_TARGET}" "${bundle_id}"
|
||||
if [[ "${push_sandbox_simulator}" == "1" ]]; then
|
||||
# shellcheck disable=SC2086
|
||||
SIMCTL_CHILD_OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET="${simulator_proof_secret}" \
|
||||
${SIMCTL_BIN} launch "${SIMULATOR_TARGET}" "${bundle_id}"
|
||||
else
|
||||
run_simctl launch "${SIMULATOR_TARGET}" "${bundle_id}"
|
||||
fi
|
||||
|
||||
@@ -64,6 +64,7 @@ describe.sequential("scripts/ios-configure-signing.sh", () => {
|
||||
|
||||
expect(stdout).toContain("team=FWJYW4S8P8 app=ai.openclawfoundation.app");
|
||||
expect(generated).toContain("OPENCLAW_DEVELOPMENT_TEAM = FWJYW4S8P8");
|
||||
expect(generated).toContain("OPENCLAW_CODE_SIGN_ENTITLEMENTS = Sources/OpenClaw.entitlements");
|
||||
expect(generated).toContain("OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app");
|
||||
expect(generated).toContain("OPENCLAW_SHARE_BUNDLE_ID = ai.openclawfoundation.app.share");
|
||||
expect(generated).toContain("OPENCLAW_APP_GROUP_ID = group.ai.openclawfoundation.app.shared");
|
||||
|
||||
@@ -79,7 +79,7 @@ describe("scripts/ios-release-signing.mjs", () => {
|
||||
expect(output).toContain("Signing branch: main");
|
||||
expect(output).toContain("Signing setup and sync: Fastlane match");
|
||||
expect(output).not.toContain("OpenClawWatchExtension");
|
||||
expect(output).toContain("capabilities: PUSH_NOTIFICATIONS, APP_GROUPS");
|
||||
expect(output).toContain("capabilities: PUSH_NOTIFICATIONS, APP_GROUPS, APP_ATTEST");
|
||||
expect(output).toContain("app groups: group.ai.openclawfoundation.app.shared");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,12 +39,18 @@ function makeFixture(bundleId: string): { root: string; script: string; logFile:
|
||||
path.join(scriptsDir, "ios-configure-signing.sh"),
|
||||
`#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ -n "\${OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET:-}" || -n "\${CUSTOM_SIMULATOR_PUSH_PROOF_SECRET:-}" ]]; then
|
||||
printf 'configure-signing-proof-env leaked\\n' >>"${logFile}"
|
||||
fi
|
||||
`,
|
||||
);
|
||||
writeExecutable(
|
||||
path.join(scriptsDir, "ios-write-version-xcconfig.sh"),
|
||||
`#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ -n "\${OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET:-}" || -n "\${CUSTOM_SIMULATOR_PUSH_PROOF_SECRET:-}" ]]; then
|
||||
printf 'write-version-proof-env leaked\\n' >>"${logFile}"
|
||||
fi
|
||||
`,
|
||||
);
|
||||
writeExecutable(
|
||||
@@ -52,6 +58,12 @@ set -euo pipefail
|
||||
`#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf 'xcodegen %s\\n' "$*" >>"${logFile}"
|
||||
if [[ -n "\${OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET:-}" ]]; then
|
||||
printf 'xcodegen-proof-env leaked\\n' >>"${logFile}"
|
||||
fi
|
||||
if [[ -n "\${CUSTOM_SIMULATOR_PUSH_PROOF_SECRET:-}" ]]; then
|
||||
printf 'xcodegen-custom-proof-env leaked\\n' >>"${logFile}"
|
||||
fi
|
||||
`,
|
||||
);
|
||||
writeExecutable(
|
||||
@@ -59,6 +71,12 @@ printf 'xcodegen %s\\n' "$*" >>"${logFile}"
|
||||
`#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf 'xcodebuild %s\\n' "$*" >>"${logFile}"
|
||||
if [[ -n "\${OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET:-}" ]]; then
|
||||
printf 'xcodebuild-proof-env leaked\\n' >>"${logFile}"
|
||||
fi
|
||||
if [[ -n "\${CUSTOM_SIMULATOR_PUSH_PROOF_SECRET:-}" ]]; then
|
||||
printf 'xcodebuild-custom-proof-env leaked\\n' >>"${logFile}"
|
||||
fi
|
||||
derived=""
|
||||
configuration="Debug"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@@ -90,6 +108,13 @@ PLIST
|
||||
`#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf 'simctl %s\\n' "$*" >>"${logFile}"
|
||||
if [[ "$1" == "launch" ]]; then
|
||||
if [[ -n "\${SIMCTL_CHILD_OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET:-}" ]]; then
|
||||
printf 'simctl-launch-proof set\\n' >>"${logFile}"
|
||||
else
|
||||
printf 'simctl-launch-proof unset\\n' >>"${logFile}"
|
||||
fi
|
||||
fi
|
||||
if [[ "$1" == "boot" ]]; then
|
||||
if [[ "\${SIMCTL_BOOT_MODE:-}" == "booted" ]]; then
|
||||
echo "Unable to boot device in current state: Booted" >&2
|
||||
@@ -113,8 +138,12 @@ sed -n 's:.*<key>CFBundleIdentifier</key><string>\\([^<]*\\)</string>.*:\\1:p' "
|
||||
return { root, script, logFile };
|
||||
}
|
||||
|
||||
function runIosRun(fixture: { root: string; script: string }, extraEnv = {}): string {
|
||||
return execFileSync(BASH_BIN, bashArgs(fixture.script), {
|
||||
function runIosRun(
|
||||
fixture: { root: string; script: string },
|
||||
extraEnv = {},
|
||||
args: string[] = [],
|
||||
): string {
|
||||
return execFileSync(BASH_BIN, [...bashArgs(fixture.script), ...args], {
|
||||
env: {
|
||||
...process.env,
|
||||
IOS_DERIVED_DATA_DIR: path.join(fixture.root, "DerivedData"),
|
||||
@@ -146,6 +175,54 @@ describe("scripts/ios-run.sh", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("builds simulator sandbox relay mode and injects proof secret only at launch", () => {
|
||||
const fixture = makeFixture("ai.openclawfoundation.app");
|
||||
const proofSecret = "x".repeat(32);
|
||||
|
||||
runIosRun(
|
||||
fixture,
|
||||
{
|
||||
OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET: proofSecret,
|
||||
SIMCTL_BOOT_MODE: "booted",
|
||||
},
|
||||
["--push-sandbox-simulator"],
|
||||
);
|
||||
|
||||
const log = readFileSync(fixture.logFile, "utf8");
|
||||
expect(log).toContain("OPENCLAW_PUSH_TRANSPORT=relay");
|
||||
expect(log).toContain("OPENCLAW_PUSH_DISTRIBUTION=official");
|
||||
expect(log).toContain(
|
||||
"OPENCLAW_PUSH_RELAY_BASE_URL=https://ios-push-relay-sandbox.openclaw.ai",
|
||||
);
|
||||
expect(log).toContain("OPENCLAW_PUSH_APNS_ENVIRONMENT=sandbox");
|
||||
expect(log).toContain("OPENCLAW_PUSH_RELAY_PROFILE=simulatorSandbox");
|
||||
expect(log).toContain("OPENCLAW_PUSH_PROOF_POLICY=internalSimulator");
|
||||
expect(log).toContain("simctl launch iPhone 17 ai.openclawfoundation.app");
|
||||
expect(log).toContain("simctl-launch-proof set");
|
||||
expect(log).not.toContain("proof-env leaked");
|
||||
expect(log).not.toContain(proofSecret);
|
||||
});
|
||||
|
||||
it("scrubs exported simulator proof secrets from normal build helpers", () => {
|
||||
const fixture = makeFixture("ai.openclawfoundation.app");
|
||||
const proofSecret = "x".repeat(32);
|
||||
const customProofSecret = "y".repeat(32);
|
||||
|
||||
runIosRun(fixture, {
|
||||
OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET: proofSecret,
|
||||
OPENCLAW_SIMULATOR_PUSH_PROOF_SECRET_ENV: "CUSTOM_SIMULATOR_PUSH_PROOF_SECRET",
|
||||
CUSTOM_SIMULATOR_PUSH_PROOF_SECRET: customProofSecret,
|
||||
SIMCTL_BOOT_MODE: "booted",
|
||||
});
|
||||
|
||||
const log = readFileSync(fixture.logFile, "utf8");
|
||||
expect(log).toContain("simctl launch iPhone 17 ai.openclawfoundation.app");
|
||||
expect(log).toContain("simctl-launch-proof unset");
|
||||
expect(log).not.toContain("proof-env leaked");
|
||||
expect(log).not.toContain(proofSecret);
|
||||
expect(log).not.toContain(customProofSecret);
|
||||
});
|
||||
|
||||
it("does not ignore simulator boot failures other than already booted", () => {
|
||||
const fixture = makeFixture("ai.openclawfoundation.app");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user