mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-10-16 10:17:13 +00:00
336 lines
19 KiB
Markdown
336 lines
19 KiB
Markdown
# Native macOS build and distribution
|
|
|
|
This builds GTK3 with the native Quartz backend, macOS menus/Dock integration,
|
|
UserNotifications, translations, bundled emoji, TLS, image loaders and supported
|
|
native plugins. It creates a self-contained `ZoiteChat.app`; end users do not
|
|
need Homebrew, XQuartz or Python. No system-wide ZoiteChat installation occurs.
|
|
|
|
The source changes are conditional on Darwin. Windows and Linux retain their
|
|
existing notification backends and feature defaults. This adds a separate
|
|
macOS workflow; it does not replace existing workflows.
|
|
|
|
## 1. Install build prerequisites
|
|
|
|
Use a Mac with a currently supported macOS release and native Homebrew. On Apple
|
|
Silicon, use ARM Homebrew (`/opt/homebrew`), not Intel Homebrew under Rosetta.
|
|
Intel builds use Intel Homebrew (`/usr/local`). Install Xcode command-line tools
|
|
and Homebrew from https://brew.sh if absent. Full current Xcode is recommended
|
|
for distribution and needed to meet the current App Store submission SDK rules.
|
|
|
|
```sh
|
|
xcode-select --install
|
|
# Run once if the command-line tools are not installed; let installation finish.
|
|
|
|
cd /path/to/zoitechat
|
|
brew bundle --file packaging/macos/Brewfile
|
|
python3 packaging/macos/build.py
|
|
open dist/ZoiteChat.app
|
|
```
|
|
|
|
The build compiles the app, runs its Meson tests, stages an installation within
|
|
`build-macos/macos-stage`, bundles and rewrites every non-system Mach-O dependency,
|
|
creates the icon and plist, audits the result, and signs it ad hoc for local use.
|
|
It refuses to overwrite an existing output app. For another build, choose a new
|
|
output path, or explicitly delete your old generated app before rebuilding:
|
|
|
|
```sh
|
|
python3 packaging/macos/build.py --output dist/ZoiteChat-test2.app --build-number 2
|
|
```
|
|
|
|
The same build directory can be reconfigured. Use a different build directory
|
|
for another architecture or the experimental store variant. Do not change
|
|
architectures within one Meson build directory.
|
|
|
|
Homebrew packages change over time; this is a repeatable procedure, not a
|
|
bit-for-bit reproducible dependency lock. The bundle records dependency versions
|
|
in `Contents/Resources/homebrew-dependencies.json`. Retain that record and the
|
|
release's source commit. For a reproducible release, archive the exact dependency
|
|
sources and build them in a controlled prefix, or maintain pinned Homebrew formulae.
|
|
|
|
## 2. Verify portability and behavior
|
|
|
|
```sh
|
|
python3 -m unittest discover -s packaging/macos/tests -v
|
|
python3 packaging/macos/bundle.py audit dist/ZoiteChat.app
|
|
python3 packaging/macos/smoke.py dist/ZoiteChat.app
|
|
```
|
|
|
|
The smoke check copies the app to a path containing spaces, verifies its signature,
|
|
runs `--version`, and keeps an isolated GUI process alive for ten seconds without
|
|
connecting to IRC. It tests startup, not interaction or long-running stability.
|
|
|
|
The audit verifies the entire dependency closure, including GTK image/input
|
|
modules, GIO TLS, Enchant providers, OpenSSL providers and ZoiteChat plugins. It
|
|
fails on missing libraries, external symlinks/search paths, architecture mismatches
|
|
or unresolved load commands. The minimum macOS version in the plist is derived
|
|
from **all** bundled binaries, including Homebrew libraries. Lowering that plist
|
|
value does not make newer dependency bottles run on an older Mac.
|
|
|
|
Before releasing, test on a second Mac without Homebrew installed:
|
|
|
|
- Launch from Finder and from a relocated/read-only location; verify Dock and
|
|
menu bar, preferences persistence, dark/light themes and normal shutdown.
|
|
- Connect with verified TLS, exercise SASL authentication and reconnect.
|
|
- Send Unicode/emoji, render SVG/PNG/JPEG icons/images, and test HTTPS image previews.
|
|
- Verify clipboard shortcuts, input methods, notifications/permission denial,
|
|
detach/reattach tabs, focus changes, unread highlights and notification clicks.
|
|
- Send/receive/resume DCC, write downloads and logs, import/export themes/settings.
|
|
- Test FiSH, Lua and DCC checksum plugins in the direct build.
|
|
- Exercise accessibility with VoiceOver and keyboard-only navigation.
|
|
|
|
Direct builds include Lua, FiSH and checksum, and retain the plugin loader.
|
|
Python/Perl are explicitly not bundled: reliable redistribution requires packaging
|
|
and auditing their interpreter runtimes, standard libraries and extensions.
|
|
Linux `/proc`/PCI sysinfo is not built on macOS. These are macOS build choices;
|
|
other platforms' plugin defaults are unchanged. macOS uses the existing password
|
|
storage fallback; this patch does not add a Keychain backend. Audit credentials
|
|
storage before adopting a stronger macOS security claim.
|
|
|
|
Desktop URL registration (`irc://` in Finder/browser) is not advertised by the
|
|
plist; command-line IRC URLs continue to work. A native open-URL event handler
|
|
and single-instance forwarding need separate integration before registering those
|
|
schemes. AppIndicator and Linux D-Bus scripting/single-instance support are not
|
|
used on macOS.
|
|
|
|
## 3. Sign and notarize for direct downloads
|
|
|
|
Local ad-hoc builds are for testing; they are not notarized releases. For public
|
|
release, enroll in Apple's Developer Program, install your **Developer ID
|
|
Application** certificate with its private key, then sign using that identity.
|
|
Use your actual certificate identity in place of the example:
|
|
|
|
```sh
|
|
python3 packaging/macos/bundle.py sign dist/ZoiteChat.app \
|
|
--identity 'Developer ID Application: YOUR NAME (TEAMID)' \
|
|
--entitlements packaging/macos/direct.entitlements
|
|
|
|
# Store credentials once in your login Keychain; enter your own values interactively.
|
|
xcrun notarytool store-credentials ZoiteChat-notary
|
|
|
|
ditto -c -k --keepParent dist/ZoiteChat.app dist/ZoiteChat-notarization.zip
|
|
xcrun notarytool submit dist/ZoiteChat-notarization.zip \
|
|
--keychain-profile ZoiteChat-notary --wait
|
|
|
|
# Only after notarization has succeeded:
|
|
xcrun stapler staple dist/ZoiteChat.app
|
|
xcrun stapler validate dist/ZoiteChat.app
|
|
spctl --assess --type execute --verbose=2 dist/ZoiteChat.app
|
|
|
|
ditto -c -k --keepParent dist/ZoiteChat.app dist/ZoiteChat-macos.zip
|
|
```
|
|
|
|
Signing runs inside-out, verifies the final bundle and enables hardened runtime.
|
|
It does not use `codesign --deep` to sign. `--deep` is used only for verification.
|
|
The direct entitlement allows users' independently signed native plugins; it
|
|
relaxes library validation, not the entire hardened runtime. No JIT or executable
|
|
memory entitlements are added. Do not change bundle contents after signing; signing
|
|
again is required after any code/resource edit. Stapling is Apple's supported
|
|
post-signing operation.
|
|
|
|
Publish exact corresponding source for ZoiteChat and satisfy the applicable
|
|
licenses of each bundled dependency. Notices and dependency metadata are included,
|
|
but those alone do not satisfy every source-distribution obligation. Review the
|
|
notarization log if rejected; the script does not silently bypass Gatekeeper.
|
|
|
|
### Automate signing and notarization with GitHub Actions
|
|
|
|
The workflow now has a separate signed/notarized release job for both
|
|
architectures. It uses Developer ID distribution for direct downloads, not Mac
|
|
App Store signing. You can manage it from Windows/Linux; GitHub's macOS runners
|
|
perform all signing, notarization and stapling.
|
|
|
|
Prepare these repository Actions secrets under **Settings → Secrets and
|
|
variables → Actions**, or store them as environment secrets in **macos-release**.
|
|
The certificate setting names are unchanged from the original instructions.
|
|
The three `APPLE_API_*` settings are new notarization credentials:
|
|
|
|
| Name | Type | Value |
|
|
| --- | --- | --- |
|
|
| `MACOS_CERTIFICATE_P12_BASE64` | Secret | Base64-encoded Developer ID Application `.p12`, including its private key |
|
|
| `MACOS_CERTIFICATE_PASSWORD` | Secret | Password used to export that `.p12` |
|
|
| `MACOS_SIGNING_IDENTITY` | Variable, or Secret | Exact `Developer ID Application: YOUR NAME (TEAMID)` identity; the matching certificate's SHA-1 fingerprint is also accepted |
|
|
| `APPLE_API_KEY_P8` | Secret | Entire plain-text App Store Connect **Team API key** `.p8` file, including PEM header/footer and newlines |
|
|
| `APPLE_API_KEY_ID` | Secret | That API key's Key ID |
|
|
| `APPLE_API_ISSUER_ID` | Secret | The Team API key's Issuer ID UUID; this is **not** your Apple Team ID |
|
|
|
|
Repository variables are under the **Variables** tab. If the identity is entered
|
|
as both a variable and a secret, the variable takes precedence. No separate
|
|
keychain password is needed; the job generates a temporary random password.
|
|
The `.p8` is the notarization API key; it does not replace the signing `.p12`.
|
|
No Apple ID password or app-specific password is used by this configuration.
|
|
|
|
To obtain the API key, open **App Store Connect → Users and Access → Integrations
|
|
→ App Store Connect API → Team Keys → Generate API Key**. Choose **Developer**
|
|
access for notarization. If API access is not enabled, the Account Holder must
|
|
request it first. Download the `.p8` and record the Key ID and Issuer ID. Use a
|
|
**Team** key with this workflow: individual API keys use different authentication
|
|
and are not supported by this issuer-based configuration. Apple allows the
|
|
private key download only once, so retain your copy securely.
|
|
|
|
For the certificate, export the Developer ID Application identity (certificate
|
|
plus private key) from Keychain Access as a password-protected `.p12`.
|
|
If you generated the CSR with OpenSSL on Linux/WSL, you can also create it there.
|
|
Assuming Apple's downloaded DER certificate has been saved as
|
|
`developerID_application.cer`, and you retained `zoitechat-signing.key`:
|
|
|
|
```sh
|
|
openssl x509 -inform DER -in developerID_application.cer \
|
|
-out zoitechat-signing.pem
|
|
openssl pkcs12 -export -inkey zoitechat-signing.key \
|
|
-in zoitechat-signing.pem -out ZoiteChat-signing.p12
|
|
|
|
# Enter the existing private-key password, then choose the .p12 export password.
|
|
# Copy the resulting Base64 text into MACOS_CERTIFICATE_P12_BASE64:
|
|
openssl base64 -A -in ZoiteChat-signing.p12 -out ZoiteChat-signing.p12.base64
|
|
|
|
# Read the certificate's commonName to obtain MACOS_SIGNING_IDENTITY:
|
|
openssl x509 -in zoitechat-signing.pem -noout -subject -nameopt multiline
|
|
```
|
|
|
|
Upload only the public CSR to Apple. Put the `.p12` Base64, export password and
|
|
API credentials in GitHub settings, never in the repository or build artifacts.
|
|
|
|
Once these patches are committed to `master` and the settings are ready:
|
|
|
|
1. Open **Actions → macOS build → Run workflow**.
|
|
2. Select **master** and enable **Sign with Developer ID and notarize using
|
|
release credentials**.
|
|
3. Run the workflow. It creates separate Apple Silicon and Intel release jobs.
|
|
4. After success, download `ZoiteChat-macos-arm64-signed-notarized` or
|
|
`ZoiteChat-macos-x86_64-signed-notarized`. The ZIP inside contains the signed,
|
|
stapled app. Actions artifacts wrap it in their own download archive.
|
|
|
|
A push of a `v*` tag also triggers the signed release jobs automatically, so wait
|
|
until credentials are configured before pushing a release tag. Normal branch
|
|
pushes, pull requests and manual runs with the checkbox off continue to build
|
|
ad-hoc test artifacts without any signing secrets. Manual credentialed releases
|
|
are restricted to `master`; pull requests cannot invoke the release job.
|
|
Missing/malformed release settings fail before the Homebrew/build steps rather
|
|
than producing an unsigned artifact marked as a release.
|
|
|
|
The release jobs use the GitHub environment **macos-release**. Repository secrets
|
|
work with it; environment secrets can override them. Configure environment
|
|
branch/tag restrictions or required reviewers if that fits your repository's
|
|
release process. This workflow does not automatically create a GitHub Release,
|
|
publish to zoitechat.org, or upload to App Store Connect; it produces downloadable
|
|
signed/notarized artifacts for you to publish.
|
|
|
|
The job imports the `.p12` into a temporary keychain, installs Apple's public
|
|
Developer ID intermediate certificates, verifies a valid matching identity,
|
|
and signs inside-out with hardened runtime and the direct plugin entitlement.
|
|
It uses the existing bundle audit and then submits a ZIP with `notarytool`.
|
|
After Apple reports **Accepted**, it staples the ticket to the `.app`, verifies
|
|
stapling/signatures and Gatekeeper acceptance, and creates a **new** final ZIP.
|
|
Private credential files and the temporary keychain are cleaned up on normal
|
|
success/failure paths; hosted runner VMs are also discarded after the job.
|
|
|
|
The default Apple wait limit is **60 minutes** within a **180-minute** release
|
|
job. A rejected, failed or still-pending submission produces no final release
|
|
ZIP. The `ZoiteChat-macos-<arch>-notarization-log` artifact retains the submission
|
|
ID, status and Apple's available diagnostic log. First submissions can take
|
|
longer; check the saved ID with `notarytool info` and your API credentials. An
|
|
accepted submission still requires stapling and repackaging before publication;
|
|
rerunning this workflow starts a new build and submission. To increase the wait
|
|
limit, update `--notary-timeout` and allow adequate job timeout for build plus wait.
|
|
|
|
Both architecture builds receive the same bundle build number for one workflow
|
|
run. The API credentials are supplied only to the config-check/release steps,
|
|
not to Homebrew, compiler tests, ordinary builds or pull-request jobs.
|
|
|
|
## 4. Mac App Store: experimental groundwork, not submission-ready
|
|
|
|
A working direct-download app is not sufficient for App Store submission. This
|
|
repository inherits GPL-2.0-or-later code from XChat/HexChat and other contributors.
|
|
App Store terms and GPL obligations require a licensing review; do not assume
|
|
that signing, a custom EULA, or relicensing only your new changes resolves rights
|
|
to inherited code. This patch changes no licenses or copyright permissions.
|
|
|
|
An isolated sandbox-test build is provided:
|
|
|
|
```sh
|
|
python3 packaging/macos/build.py --store-experimental \
|
|
--build-dir build-macos-store --output dist/ZoiteChat-store-test.app \
|
|
--build-number 1
|
|
|
|
# Local sandbox experimentation (not an uploadable distribution signature):
|
|
python3 packaging/macos/bundle.py sign dist/ZoiteChat-store-test.app \
|
|
--identity - --entitlements packaging/macos/store-experimental.entitlements
|
|
open dist/ZoiteChat-store-test.app
|
|
```
|
|
|
|
The separate store variant excludes loadable plugins and scripting runtimes.
|
|
The template grants sandboxed client/server networking, user-selected files and
|
|
Downloads. The direct version retains its plugin functionality. This is an
|
|
explicit experimental variant, not a change to Windows/Linux or direct builds.
|
|
|
|
Before a store package can be regarded as a release candidate:
|
|
|
|
1. Resolve the inherited GPL/dependency-license distribution question and review
|
|
Apple's current SDK, content/chat moderation, privacy and accessibility rules.
|
|
2. Register `org.Zoitechat` (or change the plist identifier consistently if already
|
|
owned by another developer), create App Store Connect metadata, and choose a
|
|
unique incrementing build number. Use a stable release, not a `-dev` snapshot.
|
|
3. Test sandboxed settings/logs, notifications, SASL/TLS, HTTPS and DCC on a real
|
|
Mac. GTK dialogs are not assumed to grant Powerbox access. Add native
|
|
`NSOpenPanel`/`NSSavePanel` integration and security-scoped bookmarks for
|
|
persistent access to user-selected files. The entitlement template alone does
|
|
not solve persistent file access. `/EXEC`, custom external commands, user
|
|
plugin loading and arbitrary external paths cannot be promised under sandbox.
|
|
4. Add required privacy manifest declarations based on an actual API audit of the
|
|
app and dependencies; do not submit an invented empty compliance declaration.
|
|
5. Obtain matching App Store distribution application/installer certificates and
|
|
a matching provisioning profile. Generate team-specific signing entitlements
|
|
from the profile with Xcode; do not use Developer ID or the generic local-test
|
|
template as a store distribution identity/profile.
|
|
6. Embed the matching profile as `Contents/embedded.provisionprofile` **before**
|
|
final signing. Sign embedded libraries with the distribution identity, then
|
|
the app with its profile-authorized entitlements (use `bundle.py sign` with
|
|
that identity and generated entitlement file). Package with Apple's
|
|
`productbuild`, validate and upload with Transporter/App Store Connect, then
|
|
test the App Store processed build via TestFlight before submission.
|
|
|
|
Example **after those prerequisites are satisfied**:
|
|
|
|
```sh
|
|
# Use the actual prepared profile and team-specific entitlements:
|
|
cp /path/to/ZoiteChat.provisionprofile \
|
|
dist/ZoiteChat-store-test.app/Contents/embedded.provisionprofile
|
|
python3 packaging/macos/bundle.py sign dist/ZoiteChat-store-test.app \
|
|
--identity 'YOUR APP STORE APPLICATION CERTIFICATE IDENTITY' \
|
|
--entitlements /path/to/ZoiteChat-distribution.entitlements
|
|
productbuild --component dist/ZoiteChat-store-test.app /Applications \
|
|
--sign 'YOUR APP STORE INSTALLER CERTIFICATE IDENTITY' dist/ZoiteChat-store.pkg
|
|
```
|
|
|
|
No App Store approval, sandbox compatibility or legal compatibility is asserted.
|
|
For an initial macOS release, the signed/notarized direct build is the practical
|
|
path while those issues are resolved.
|
|
|
|
## 5. CI and architecture support
|
|
|
|
`.github/workflows/macos-build.yml` adds independent Apple Silicon and Intel jobs.
|
|
Ordinary jobs install prerequisites, run tests, create/audit/ad-hoc-sign the
|
|
bundle, check relocated startup and upload test ZIPs. They need no Apple secrets.
|
|
The separate release jobs add Developer ID signing, Apple notarization, stapling
|
|
and final verification. Only their successful output is named signed/notarized.
|
|
|
|
Homebrew may build unsupported Intel bottles from source; that can be slower.
|
|
The workflow uses native runners and does not claim a universal application.
|
|
Do not merge only the main executable with `lipo`: a universal release must merge
|
|
and validate every bundled dylib, module and plugin plus matching resources.
|
|
Signing must happen after that merge. Publish separate architecture downloads
|
|
until a full universal dependency pipeline is available.
|
|
|
|
## References
|
|
|
|
- GTK on macOS: https://www.gtk.org/docs/installations/macos
|
|
- GTK macOS backend: https://docs.gtk.org/gtk3/macos.html
|
|
- Apple distribution signing: https://developer.apple.com/documentation/xcode/creating-distribution-signed-code-for-the-mac
|
|
- Apple packaging: https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution
|
|
- GitHub certificate import: https://docs.github.com/en/actions/how-tos/deploy/deploy-to-third-party-platforms/sign-xcode-applications
|
|
- Apple notarization authentication: https://developer.apple.com/documentation/technotes/tn3147-migrating-to-the-latest-notarization-tool
|
|
- App Store Connect API keys: https://developer.apple.com/help/app-store-connect/get-started/app-store-connect-api/
|
|
- Apple review rules (especially 1.2 and 2.4.5): https://developer.apple.com/app-store/review/guidelines/
|
|
- Apple sandbox file access: https://developer.apple.com/documentation/security/accessing-files-from-the-macos-app-sandbox
|
|
- FSF discussion of GPL and App Store terms (historical, not a current legal determination): https://www.fsf.org/blogs/licensing/more-about-the-app-store-gpl-enforcement
|