eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1name: LChat
2options:
3 bundleIdPrefix: com.elevenmessenger
4 deploymentTarget:
5 iOS: "17.0"
6 createIntermediateGroups: true
7settings:
8 base:
9 SWIFT_VERSION: "5.0"
10 MARKETING_VERSION: "1.0"
11 CURRENT_PROJECT_VERSION: "2"
12 TARGETED_DEVICE_FAMILY: "1,2"
13 # The Eleven Messenger Apple Developer team. Overridable on the xcodebuild
14 # line for building with a personal team instead.
15 DEVELOPMENT_TEAM: J5NDF5PCD8
16 # One knob for the app id; the extension derives from it so a per-machine
17 # override (e.g. LCHAT_APP_BUNDLE_ID=com.example.mine on the xcodebuild
18 # line for device signing) can never collide the two bundle ids.
19 LCHAT_APP_BUNDLE_ID: com.elevenmessenger.messenger
20packages:
21 # Mac-only auto-update framework (LChatMac). Pin a specific release rather
22 # than floating on `from:` so `xcodegen generate` is reproducible; bump
23 # deliberately and re-check ios/release-mac.sh still matches the CLI tool
24 # flags for that version.
25 Sparkle:
26 url: https://github.com/sparkle-project/Sparkle
27 exactVersion: 2.9.4
28 # The platform-neutral core (crypto, models, API, thread store) shared by the
29 # iOS app, the native Mac app and the notification extension. A local package
30 # so `swift test` runs its cross-implementation crypto vectors on the Mac
31 # directly — see LChatCore/Package.swift and docs/native-chat.md.
32 LChatCore:
33 path: LChatCore
34targets:
35 LChat:
36 type: application
37 platform: iOS
38 sources:
39 - path: LChat
40 dependencies:
41 - target: LChatNSE
42 - target: LChatShare
43 - package: LChatCore
44 settings:
45 base:
46 PRODUCT_BUNDLE_IDENTIFIER: $(LCHAT_APP_BUNDLE_ID)
47 CODE_SIGN_STYLE: Automatic
48 CODE_SIGN_ENTITLEMENTS: LChat/LChat.entitlements
49 ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
50 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
51 configs:
52 # Debug builds use ?mode=developer on the webcredentials association so
53 # dev installs bind to home immediately (no Apple-CDN wait; needs the
54 # device's Developer Mode, which dev installs require anyway). Release
55 # keeps the clean association — the Debug file must never ship.
56 Debug:
57 CODE_SIGN_ENTITLEMENTS: LChat/LChat-Debug.entitlements
58 info:
59 path: LChat/Info.plist
60 properties:
61 # Drive the bundle version from the build settings (single source of
62 # truth) — xcodegen otherwise bakes a literal "1", so bumping
63 # CURRENT_PROJECT_VERSION for a new TestFlight build had no effect.
64 CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
65 CFBundleShortVersionString: "$(MARKETING_VERSION)"
66 # Only OS-standard crypto (TLS + CryptoKit/WebCrypto) — exempt, so TestFlight
67 # builds don't stall on the per-build "Missing Compliance" prompt.
68 ITSAppUsesNonExemptEncryption: false
69 CFBundleDisplayName: Eleven
70 NSCameraUsageDescription: Camera access lets you take photos to share in chats and scan QR codes for joining
71 UILaunchScreen: {}
72 # Communication notifications (sender's photo on message banners): the
73 # system requires the intent to be declared here, alongside the
74 # com.apple.developer.usernotifications.communication entitlement.
75 NSUserActivityTypes:
76 - INSendMessageIntent
77 # The welcome page's "Open the app" button fires
78 # elevenmessenger://add?url=<invite link> (see web/pages/join.html).
79 CFBundleURLTypes:
80 - CFBundleURLName: com.elevenmessenger.messenger
81 CFBundleURLSchemes:
82 - elevenmessenger
83 UISupportedInterfaceOrientations:
84 - UIInterfaceOrientationPortrait
85 UISupportedInterfaceOrientations~ipad:
86 - UIInterfaceOrientationPortrait
87 - UIInterfaceOrientationPortraitUpsideDown
88 - UIInterfaceOrientationLandscapeLeft
89 - UIInterfaceOrientationLandscapeRight
90 # E2E instances are served over HTTPS in production. This single
91 # exception only loosens ATS for loopback/.local addresses so you can
92 # test against a `go run .` instance during development.
93 NSAppTransportSecurity:
94 NSAllowsLocalNetworking: true
95 # Background App Refresh: periodic wakes sync + decrypt new messages
96 # into the local store (BackgroundRefresh.swift / BackgroundSync).
97 UIBackgroundModes:
98 - fetch
99 BGTaskSchedulerPermittedIdentifiers:
100 - $(PRODUCT_BUNDLE_IDENTIFIER).refresh
101
102 # A genuinely native macOS app (NOT Catalyst). Shares the platform-neutral core
103 # (LChatCore) with the iOS target; its own identity model + all crypto live
104 # elsewhere (LChatMac/MacInstance.swift + the WKWebView). See LChatMac/.
105 LChatMac:
106 type: application
107 platform: macOS
108 deploymentTarget: "14.0"
109 sources:
110 - path: LChatMac
111 dependencies:
112 - package: Sparkle
113 - package: LChatCore
114 settings:
115 base:
116 # Dev alts (./tools/dev alt <slug>): one knob-set turns this target
117 # into a side-by-side dev flavour — distinct bundle id (its own
118 # sandbox container + Launch Services identity, so it runs alongside
119 # the Sparkle-updated app), "Eleven Dev" naming, the orange icon, and
120 # Sparkle's auto-check off. The keychain access group is unchanged,
121 # so an alt sees the same signed-in spaces (identities + home session
122 # ride the team-shared group, not the bundle id).
123 LCHAT_MAC_ALT_SUFFIX: ""
124 LCHAT_MAC_DISPLAY_NAME: Eleven
125 LCHAT_MAC_APPICON: AppIcon
126 LCHAT_MAC_SPARKLE_AUTOCHECK: "YES"
127 PRODUCT_NAME: $(LCHAT_MAC_DISPLAY_NAME)
128 PRODUCT_BUNDLE_IDENTIFIER: com.elevenmessenger.messenger.mac$(LCHAT_MAC_ALT_SUFFIX)
129 CODE_SIGN_STYLE: Automatic
130 CODE_SIGN_ENTITLEMENTS: LChatMac/LChatMac.entitlements
131 ASSETCATALOG_COMPILER_APPICON_NAME: $(LCHAT_MAC_APPICON)
132 # Required for notarization (Developer ID distribution, see
133 # ios/release-mac.sh). Sparkle's bundled XPC helpers need no entitlement
134 # exceptions on top of this for a non-sandboxed app.
135 ENABLE_HARDENED_RUNTIME: YES
136 # The "Embed eleven CLI" phase runs `go build` (GOCACHE lives outside
137 # the build dir), which Xcode's script sandbox would refuse.
138 ENABLE_USER_SCRIPT_SANDBOXING: NO
139 configs:
140 # Debug carries the webcredentials association for Mac "Sign in with
141 # Eleven" (?mode=developer — never ships; Release keeps the plain
142 # entitlements). Needs a real Development profile: this Mac is
143 # registered in the team (device 8VWU966HQ3, 2026-07-08).
144 Debug:
145 CODE_SIGN_ENTITLEMENTS: LChatMac/LChatMac-Dev.entitlements
146 # Bundle the eleven CLI (../eleven, docs/eleven-cli.md) at
147 # Contents/Helpers/eleven so Settings can offer "install the command-line
148 # tool" — the app and the CLI ship as one artifact. Built universal
149 # (arm64+x86_64, CGO off) and signed here because Xcode only auto-signs
150 # nested code in Frameworks/PlugIns; hardened runtime + (release) a secure
151 # timestamp keep notarization happy. Runs before the product's own CodeSign
152 # step, so the app seal covers it.
153 postBuildScripts:
154 # Stamp the commit the app was built from into the bundle, so the About
155 # panel can show it (BuildInfo.swift) — the answer to "which build is
156 # this?" for a locally-built app. A custom Info.plist key on purpose:
157 # CFBundleVersion is how Sparkle orders updates, so the SHA must never
158 # go there. Runs before the product's CodeSign step, like the phase
159 # below, so the seal covers the edited plist. No outputs (and dependency
160 # analysis off) so it re-stamps on EVERY build — otherwise switching
161 # branches would leave the previous SHA in the bundle. Silently skipped
162 # when there's no git to ask (a source tarball), rather than failing.
163 - name: Stamp git SHA
164 shell: /bin/bash
165 basedOnDependencyAnalysis: false
166 script: |
167 set -euo pipefail
168 SHA="$(git -C "$SRCROOT/.." rev-parse --short HEAD 2>/dev/null || true)"
169 [ -n "$SHA" ] || exit 0
170 # A local build usually has edits in flight; say so, or the SHA
171 # implies the running app matches that commit when it doesn't.
172 if [ -n "$(git -C "$SRCROOT/.." status --porcelain 2>/dev/null)" ]; then
173 SHA="$SHA-dirty"
174 fi
175 # $INFOPLIST_PATH, not a hardcoded name: PRODUCT_NAME varies (a dev
176 # alt's bundle is "Eleven Dev.app").
177 plutil -replace GitSHA -string "$SHA" "$TARGET_BUILD_DIR/$INFOPLIST_PATH"
178 - name: Embed eleven CLI
179 shell: /bin/bash
180 outputFiles:
181 - $(TARGET_BUILD_DIR)/$(CONTENTS_FOLDER_PATH)/Helpers/eleven
182 script: |
183 set -euo pipefail
184 GO="$(command -v go || true)"
185 # Xcode's PATH is minimal — look where Go installs actually live.
186 for g in /usr/local/go/bin/go /opt/homebrew/bin/go /usr/local/bin/go; do
187 [ -n "$GO" ] && break
188 [ -x "$g" ] && GO="$g"
189 done
190 [ -n "$GO" ] || { echo "error: embedding the eleven CLI needs Go on this machine (brew install go)" >&2; exit 1; }
191 HELPERS="$TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/Helpers"
192 mkdir -p "$HELPERS"
193 TMPD="$(mktemp -d)"; trap 'rm -rf "$TMPD"' EXIT
194 cd "$SRCROOT/.."
195 # go.mod may require a newer Go than the machine's (1.25 since the
196 # litestream bump); auto lets Go fetch the right toolchain instead
197 # of failing under a machine-pinned GOTOOLCHAIN=local.
198 export GOTOOLCHAIN=auto
199 CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 "$GO" build -trimpath -o "$TMPD/eleven-arm64" ./eleven
200 CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 "$GO" build -trimpath -o "$TMPD/eleven-amd64" ./eleven
201 lipo -create -output "$HELPERS/eleven" "$TMPD/eleven-arm64" "$TMPD/eleven-amd64"
202 TS=()
203 [ "$CONFIGURATION" = "Release" ] && TS=(--timestamp)
204 codesign --force --options runtime ${TS[@]+"${TS[@]}"} \
205 --sign "${EXPANDED_CODE_SIGN_IDENTITY:--}" "$HELPERS/eleven"
206 info:
207 path: LChatMac/Info.plist
208 properties:
209 CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
210 CFBundleShortVersionString: "$(MARKETING_VERSION)"
211 CFBundleDisplayName: $(LCHAT_MAC_DISPLAY_NAME)
212 CFBundleName: $(LCHAT_MAC_DISPLAY_NAME)
213 LSApplicationCategoryType: public.app-category.social-networking
214 # Same custom scheme as the iOS app, so the welcome page's "Open the app"
215 # button works on the Mac too. (No associated domains here — the Mac app
216 # is ad-hoc signed, so universal links can't validate.)
217 # The scheme carries the alt suffix so a dev alt never captures the
218 # browser sign-in handoff (elevenmessenger://) from the real app —
219 # alts don't need it anyway; they read the shared keychain session.
220 CFBundleURLTypes:
221 - CFBundleURLName: com.elevenmessenger.messenger
222 CFBundleURLSchemes:
223 - elevenmessenger$(LCHAT_MAC_ALT_SUFFIX)
224 NSAppTransportSecurity:
225 NSAllowsLocalNetworking: true
226 # Sparkle auto-update feed, published by ios/release-mac.sh to the
227 # separate public elevenmessenger/releases repo (this repo is private,
228 # so GitHub Releases here aren't publicly fetchable). The "latest"
229 # alias always resolves to whatever release-mac.sh most recently
230 # published. SUPublicEDKey is generated once via Sparkle's
231 # generate_keys tool — see ios/release-mac.sh for the matching private
232 # key usage.
233 SUFeedURL: https://github.com/elevenmessenger/releases/releases/latest/download/appcast.xml
234 SUPublicEDKey: "TkXBJrY0JvTeeCaHbEZlT5v5apJaOsG1op8uq7n1TsM="
235 # Skips Sparkle's own first-run "should Eleven check for updates
236 # automatically?" prompt — background checking is the default we want.
237 # A string on purpose (Sparkle reads it with -boolValue): dev alts
238 # pass NO so they never offer to "update" themselves to stable.
239 SUEnableAutomaticChecks: $(LCHAT_MAC_SPARKLE_AUTOCHECK)
240 # The app is sandboxed (LChatMac.entitlements), so Sparkle installs
241 # updates through its bundled InstallerLauncher XPC service instead of
242 # spawning the installer directly. Its Downloader service stays off —
243 # the app has com.apple.security.network.client, which Sparkle prefers.
244 SUEnableInstallerLauncherService: true
245 # Communication notifications (sender's photo/monogram on banners):
246 # declared alongside the entitlement, matching the iOS target.
247 NSUserActivityTypes:
248 - INSendMessageIntent
249 # Notification service extension: decrypts pushed ciphertext on-device (the
250 # E2EE half of APNs). Shares the crypto/keychain code with the app via LChatCore.
251 LChatNSE:
252 type: app-extension
253 platform: iOS
254 sources:
255 - path: LChatNSE
256 dependencies:
257 - package: LChatCore
258 settings:
259 base:
260 PRODUCT_BUNDLE_IDENTIFIER: $(LCHAT_APP_BUNDLE_ID).nse
261 CODE_SIGN_ENTITLEMENTS: LChatNSE/LChatNSE.entitlements
262 info:
263 path: LChatNSE/Info.plist
264 properties:
265 # Must match the host app's version, or App Store validation rejects the
266 # bundle (extension/app version mismatch).
267 CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
268 CFBundleShortVersionString: "$(MARKETING_VERSION)"
269 CFBundleDisplayName: Eleven Notifications
270 # Communication notifications: declared alongside the entitlement in
271 # LChatNSE.entitlements (the restyle runs in this process), matching
272 # the app target's declaration.
273 NSUserActivityTypes:
274 - INSendMessageIntent
275 NSExtension:
276 NSExtensionPointIdentifier: com.apple.usernotifications.service
277 NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).NotificationService
278 # Share extension: receives images/videos from the system share sheet into
279 # an app-group inbox; the app picks them up on next activation and asks
280 # which chat to send them to. No keys, no network — just a byte ferry.
281 LChatShare:
282 type: app-extension
283 platform: iOS
284 sources:
285 - path: LChatShare
286 dependencies:
287 - package: LChatCore
288 settings:
289 base:
290 PRODUCT_BUNDLE_IDENTIFIER: $(LCHAT_APP_BUNDLE_ID).share
291 CODE_SIGN_ENTITLEMENTS: LChatShare/LChatShare.entitlements
292 info:
293 path: LChatShare/Info.plist
294 properties:
295 CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
296 CFBundleShortVersionString: "$(MARKETING_VERSION)"
297 CFBundleDisplayName: Eleven
298 NSExtension:
299 NSExtensionPointIdentifier: com.apple.share-services
300 NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).ShareViewController
301 NSExtensionAttributes:
302 NSExtensionActivationRule:
303 NSExtensionActivationSupportsImageWithMaxCount: 6
304 NSExtensionActivationSupportsMovieWithMaxCount: 1
305
306 # End-to-end UI tests (Simulator only). They need a live server and an invite
307 # link, passed as TEST_RUNNER_INVITE_LINK on the xcodebuild invocation; without
308 # it the test skips itself. See LChatUITests/JoinAndChatTests.swift.
309 LChatUITests:
310 type: bundle.ui-testing
311 platform: iOS
312 sources:
313 - path: LChatUITests
314 dependencies:
315 - target: LChat
316 settings:
317 GENERATE_INFOPLIST_FILE: YES
318
319 # In-process feed tests (Simulator): mount the REAL feed container in a
320 # window against a fake ChatFeedModel — measurement/layout geometry is
321 # testable without a server or a device (born from the short-chat bunching
322 # triage, 2026-07-21).
323 LChatFeedTests:
324 type: bundle.unit-test
325 platform: iOS
326 sources:
327 - path: LChatFeedTests
328 dependencies:
329 - target: LChat
330 settings:
331 GENERATE_INFOPLIST_FILE: YES
332 TEST_HOST: $(BUILT_PRODUCTS_DIR)/LChat.app/LChat
333 BUNDLE_LOADER: $(TEST_HOST)
334schemes:
335 LChat:
336 build:
337 targets:
338 LChat: all
339 run:
340 config: Debug
341 test:
342 config: Debug
343 targets:
344 - LChatUITests
345 - LChatFeedTests
346 # Needed to archive/export the Mac app at all (see ios/release-mac.sh).
347 LChatMac:
348 build:
349 targets:
350 LChatMac: all
351 run:
352 config: Debug
353 archive:
354 config: Release
355