Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/_ci-clients.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ jobs:
if: inputs.lane == 'mobile-swift' && inputs.mobile
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Test fork iPhone setup with macOS Bash
run: /bin/bash scripts/test-mobile-ios-setup.sh
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- name: Install Flutter dependencies
run: cd mobile && flutter pub get
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ jobs:
run: scripts/test-desktop-instance-detection.sh
- name: Mobile worktree identity contract
run: scripts/test-mobile-worktree-overrides.sh
- name: Mobile fork iPhone setup
run: /bin/bash scripts/test-mobile-ios-setup.sh
- name: Codex security review contract
run: just security-review-check
- name: Rust cache contract
Expand Down
8 changes: 8 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,14 @@ mobile_dir := "mobile"
mobile-install:
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && flutter pub get

# Save this fork's iPhone signing identity (no Apple credentials are stored)
mobile-ios-setup bundle_id team_id name="Buzz Voltage":
/bin/bash scripts/mobile-ios-setup.sh {{quote(bundle_id)}} {{quote(team_id)}} {{quote(name)}}

# Install on a named iPhone from a Mac; PHONE_ID comes from flutter devices
mobile-ios-device device:
/bin/bash scripts/mobile-ios-device.sh {{quote(device)}}

# Format all Dart code
mobile-fmt:
unset GIT_DIR GIT_WORK_TREE; cd {{mobile_dir}} && dart format .
Expand Down
15 changes: 15 additions & 0 deletions docs/mobile-remote-agent-launch.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ Older managers return an unsupported-operation error that mobile displays as an
Unregistered agents display the one-time desktop setup instruction.
This change does not deploy a manager, sync ArgoCD, or change production permissions.

## Why existing mobile builds need an update

Existing mobile builds publish the owner's signed channel message, including its addressed agent and thread tags.
They do not send the separate kind 24210 `start` request.
The manager currently subscribes only to kind 24210 requests addressed to its key, so it never receives those channel mentions.

This is an implementation boundary, not a signature or protocol limitation.
A server-side mention watcher could support existing clients using the saved launch registration and the same owner and thread checks.
That watcher is not implemented here.
It must receive messages through an identity with access to the agent's channels, preserve community boundaries, and reconcile duplicates and reconnect gaps.
The manager's service identity does not automatically inherit each developer's private channel memberships.

A web client can also send the same `start` operation without changing the cluster agent protocol.
It still needs the user's signing identity, the registered agent, and verified manager replies.

## Phone acceptance test

Use an owned staging Remote workspace agent and a disposable test thread.
Expand Down
100 changes: 100 additions & 0 deletions mobile/IOS_FORK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Install your fork on an iPhone

Build this checkout with your Apple team's signing identity.
Your fork can coexist with the older Buzz app under its own bundle ID.
Later builds update that fork when you keep the bundle ID and signing team unchanged.
An upstream TestFlight installation does not receive changes merged only into this fork.

## Before the first install

Use a Mac with full Xcode, CocoaPods, and access to an Apple Developer Program team.
These instructions retain Buzz's existing capabilities; a free Personal Team may not support them.
Open Xcode once to install its components and accept its license.
Sign in under **Xcode → Settings → Apple Accounts**.

Clone `voltagecloud/buzz`, or update your clean checkout to the reviewed revision on `main`.
From the repository root, activate the pinned tools:

```bash
. ./bin/activate-hermit
just mobile-ios-setup io.voltage.buzz.mobile YOURTEAMID "Buzz Voltage"
```

Replace `YOURTEAMID` with your ten-character Apple Team ID.
Choose a bundle ID that your team owns; `io.voltage.buzz.mobile` is an example.
The command writes `mobile/ios/Flutter/AppOverrides.xcconfig`, which Git ignores.
It preserves unrelated overrides, including your push gateway and capability environments.
It does not create Apple registrations, certificates, or provisioning profiles.

Ask your Apple team administrator to provision these identifiers:

| Identifier | Required configuration |
| --- | --- |
| Your bundle ID | App Groups, Push Notifications, App Attest, Declared Age Range, and Communication Notifications |
| `<bundle-id>.NotificationService` | App Groups |
| `group.<bundle-id>` | Associate this App Group with both targets |

Use the entitlements in `ios/Runner/Runner.entitlements` and `ios/NotificationService/NotificationService.entitlements` as the source of truth.
After enabling capabilities, regenerate any manually managed profiles.
The setup command selects automatic signing; Xcode still needs access to the matching team and capabilities.

Connect and unlock your iPhone, then trust the Mac when prompted.
Enable **Settings → Privacy & Security → Developer Mode** on the phone when Xcode requests it.

## Install or update

Find the physical iPhone's device ID:

```bash
flutter devices
```

Run the device command with that ID:

```bash
just mobile-ios-device YOUR_IPHONE_DEVICE_ID
```

The command activates Hermit, resolves Flutter packages, installs native dependencies through Flutter, and runs the debug build.
It uses your saved signing identity, including when the checkout is a Git worktree.
If signing needs attention, open `mobile/ios/Runner.xcworkspace` in Xcode.
Select your phone and inspect **Signing & Capabilities** for both **Runner** and **NotificationService**.

For later updates, update the same checkout to the reviewed revision and repeat the device command.
No checkout reset, app uninstall, or identity change is needed.
Sign into the fork with your existing Buzz identity and the same community.
Keep the older app until you have confirmed that identity and community access in the fork.

## Push and agent launch

Push is optional for running Buzz and starting agents while the app is open.
Without a configured gateway, the build leaves push unavailable.
Do not reuse an upstream gateway for a different bundle ID.
Push setup requires matching gateway application identifiers and signing environments; see [the mobile README](README.md#ios-push-capability).

The server must also support mobile launches, and each agent needs its one-time desktop registration.
See [the launch rollout](../docs/mobile-remote-agent-launch.md#rollout).
Installing this app does not deploy the manager or grant cluster permissions.

## TestFlight distribution

For installation without a development Mac, an Apple team member must sign and upload a build of this fork.
Use a stable bundle ID and grant testers access in your own App Store Connect application.
The inherited `mobile-release.sh` and release-candidate workflow target `block/buzz`; they do not publish this fork.
This setup command prepares local device builds, not an automated TestFlight release pipeline.

## Troubleshooting

| Symptom | Next action |
| --- | --- |
| No phone in `flutter devices` | Unlock it, trust the Mac, and check Xcode's Devices and Simulators window. |
| Unsupported entitlement or profile mismatch | Have the team administrator enable the listed capability and refresh the profile. |
| Bundle ID belongs to another team | Choose your team's ID and rerun setup; this installs a separate app. |
| Installation cannot replace the old app | Confirm both its bundle ID and signing identity; use a separate fork ID when they differ. |
| `unauthorized_owner` on agent startup | An administrator must provision your Buzz public key in the staging developer list. |
| One-time desktop setup message | Start that agent once from desktop after the manager upgrade, then retry from mobile. |

[Apple's device instructions](https://developer.apple.com/documentation/xcode/running-your-app-on-simulated-or-physical-devices)
describe signing and device setup.
[Flutter's iOS setup](https://docs.flutter.dev/platform-integration/ios/setup)
describes the host tools.
2 changes: 2 additions & 0 deletions mobile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Flutter mobile client for Buzz.

To install your own fork on a physical iPhone, follow [the fork setup guide](IOS_FORK.md).

See [VISION_MOBILE.md](../VISION_MOBILE.md) for intended behavior and
architecture.

Expand Down
49 changes: 49 additions & 0 deletions scripts/mobile-ios-device.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
# Use the checkout's pinned Flutter and saved fork identity on a named device.
set -euo pipefail

if [[ $# -ne 1 || -z "$1" || "$1" == -* ]]; then
echo "Usage: scripts/mobile-ios-device.sh PHONE_ID (from flutter devices)" >&2
exit 2
fi
if [[ "$(uname -s)" != Darwin ]]; then
echo "Installing on an iPhone requires a Mac with Xcode. See mobile/IOS_FORK.md." >&2
exit 1
fi

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
config="$repo_root/mobile/ios/Flutter/AppOverrides.xcconfig"
if [[ ! -f "$config" ]] || \
! grep -Eq '^[[:space:]]*BUNDLE_IDENTIFIER[[:space:]]*=[[:space:]]*[^[:space:]]+' "$config" || \
! grep -Eq '^[[:space:]]*BUZZ_DEVELOPMENT_TEAM[[:space:]]*=[[:space:]]*[A-Z0-9]{10}[[:space:]]*$' "$config"; then
echo "Configure your fork first: just mobile-ios-setup BUNDLE_ID TEAM_ID" >&2
exit 1
fi
if ! xcodebuild -version >/dev/null 2>&1; then
echo "Select a full Xcode installation with xcode-select, and complete Xcode's first-run setup." >&2
exit 1
fi
if ! command -v pod >/dev/null 2>&1; then
echo "Install CocoaPods on your Mac before running this command. See mobile/IOS_FORK.md." >&2
exit 1
fi

cd "$repo_root"
unset GIT_DIR GIT_WORK_TREE
# shellcheck source=../bin/activate-hermit
. ./bin/activate-hermit
./scripts/mobile-worktree-overrides.sh
cd mobile
flutter pub get

# Match mobile-dev: an explicit origin wins, otherwise use the Xcode override.
# Never evaluate xcconfig as shell code; its https:/$()/ spelling is literal.
gateway_url="${BUZZ_PUSH_GATEWAY_URL:-}"
if [[ -z "$gateway_url" ]]; then
gateway_url="$(sed -nE 's/^[[:space:]]*BUZZ_PUSH_GATEWAY_URL[[:space:]]*=[[:space:]]*(.*[^[:space:]])[[:space:]]*$/\1/p' "$config" | tail -n 1 | sed 's/\$()//g')"
fi
set -- run --debug --no-pub --device-id "$1"
if [[ -n "$gateway_url" ]]; then
set -- "$@" "--dart-define=BUZZ_PUSH_GATEWAY_URL=$gateway_url"
fi
exec flutter "$@"
78 changes: 78 additions & 0 deletions scripts/mobile-ios-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/bash
# Configure a fork's iPhone identity without editing tracked Xcode files.
set -euo pipefail

usage() {
cat <<'USAGE'
Usage: scripts/mobile-ios-setup.sh BUNDLE_ID TEAM_ID [DISPLAY_NAME]

Example: scripts/mobile-ios-setup.sh io.voltage.buzz.mobile ABCDE12345 "Buzz Voltage"

Writes the gitignored mobile/ios/Flutter/AppOverrides.xcconfig. Other settings,
including a configured push gateway, are preserved. Use the same bundle ID and
team for subsequent updates. This does not register Apple IDs or signing profiles.
USAGE
}

if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
if [[ $# -lt 2 || $# -gt 3 ]]; then
usage >&2
exit 2
fi

bundle_id="$1"
team_id="$2"
display_name="${3-Buzz Voltage}"
if [[ ! "$bundle_id" =~ ^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)+$ || ${#bundle_id} -gt 235 ]]; then
echo "Use a reverse-domain bundle ID, with room for .NotificationService (at most 235 characters)." >&2
exit 2
fi
if [[ "$bundle_id" == "xyz.block.buzz.mobile" || "$bundle_id" == "xyz.block.buzz.dogfood.mobile" ]]; then
echo "Choose a bundle ID owned by your Apple team, rather than the upstream Buzz IDs." >&2
exit 2
fi
if [[ ! "$team_id" =~ ^[A-Z0-9]{10}$ ]]; then
echo "Apple Team ID must contain exactly 10 uppercase letters or digits." >&2
exit 2
fi
if [[ -z "$display_name" || ${#display_name} -gt 64 || ! "$display_name" =~ ^[A-Za-z0-9._\(\)\ -]+$ ]]; then
echo "Display name must contain 1–64 letters, numbers, spaces, periods, underscores, parentheses, or hyphens." >&2
exit 2
fi

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
config_dir="$repo_root/mobile/ios/Flutter"
config="$config_dir/AppOverrides.xcconfig"
if [[ ! -d "$config_dir" || -L "$config" || ( -e "$config" && ! -f "$config" ) ]]; then
echo "Run from a Buzz checkout with a regular AppOverrides.xcconfig file, or no existing override file." >&2
exit 1
fi

existing=/dev/null
[[ ! -f "$config" ]] || existing="$config"
temporary="$(mktemp "$config_dir/.AppOverrides.xcconfig.XXXXXX")"
trap 'rm -f "$temporary"' EXIT

# Filter only the four managed settings. Keep comments, gateway definitions,
# capability environments, and other operator settings byte-for-byte per line.
awk '!/^[[:space:]]*(BUNDLE_IDENTIFIER|BUZZ_DEVELOPMENT_TEAM|APP_DISPLAY_NAME|CODE_SIGN_STYLE)[[:space:]]*=/' \
"$existing" > "$temporary"
cat >> "$temporary" <<XCCONFIG
BUNDLE_IDENTIFIER = $bundle_id
BUZZ_DEVELOPMENT_TEAM = $team_id
APP_DISPLAY_NAME = $display_name
CODE_SIGN_STYLE = Automatic
XCCONFIG

if [[ -f "$config" ]] && cmp -s "$temporary" "$config"; then
echo "iPhone identity already configured."
else
mv "$temporary" "$config"
echo "Configured mobile/ios/Flutter/AppOverrides.xcconfig (gitignored)."
fi
printf 'App: %s\nTeam: %s\nExtension: %s.NotificationService\nApp Group: group.%s\n' \
"$bundle_id" "$team_id" "$bundle_id" "$bundle_id"
echo "Next: follow mobile/IOS_FORK.md for Apple capabilities, then run just mobile-ios-device PHONE_ID on your Mac."
Loading
Loading