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
18 changes: 18 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,21 @@ jobs:
bash scripts/build-app.sh
test -f dist/mx3-lite.app/Contents/Resources/defaults.json
test ! -e dist/mx3-lite.app/Contents/Resources/default-config.json

release-archives:
runs-on: macos-14
strategy:
matrix:
arch: [arm64, x86_64]
steps:
- uses: actions/checkout@v4
- name: Check release scripts
run: bash -n scripts/build-release-app.sh scripts/package-release.sh scripts/verify-release.sh
- name: Build and verify ${{ matrix.arch }} validation archive
run: bash scripts/package-release.sh --arch "${{ matrix.arch }}"
- name: Upload validation archive
uses: actions/upload-artifact@v4
with:
name: mx3-lite-${{ matrix.arch }}-validation
path: dist/releases/v*/
if-no-files-found: error
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,18 @@ Only one action is sent each time the gesture button is held. The default moveme
## Install the app

1. Open the [latest mx3-lite release](https://github.com/KulmanD/Mx-Master-3-For-mac-lightweight-swiftBased/releases/latest).
2. Under **Assets**, download the file whose name starts with `mx3-lite-app-` and ends with `.zip`. Do not download the source ZIP unless you want to build the app yourself.
2. Under **Assets**, download the app ZIP for your Mac:
- Apple Silicon (M1, M2, M3, M4, or newer): file ending in `macos-arm64.zip`
- Intel Mac: file ending in `macos-x86_64.zip`
Do not download GitHub's automatic source ZIP unless you want to build the app yourself.
3. Open the downloaded ZIP.
4. Move **mx3-lite.app** into your **Applications** folder.
5. Control-click **mx3-lite.app**, choose **Open**, then confirm **Open**.

The downloadable app is locally signed but not notarized by Apple, so macOS may say that it cannot verify the developer on first launch. Control-clicking and choosing Open approves this specific app without disabling Gatekeeper for the rest of your Mac.
An official release should be Developer ID signed and notarized by Apple. If
you build the validation archive yourself, it is only ad-hoc signed and macOS
may require Control-click, **Open** on first launch. This approves that specific
app without disabling Gatekeeper for the rest of your Mac.

## Allow macOS permissions

Expand Down Expand Up @@ -160,4 +166,8 @@ Your settings file is kept separately so an update or reinstall does not erase y

Source-build instructions, terminal commands, configuration details, inspector mode, and debug tools are in the [technical guide](config/README.md).

Maintainers preparing signed Apple Silicon and Intel downloads should follow
the [public release guide](docs/PUBLIC_RELEASE.md). Release credentials are
owner-controlled and are never stored in this repository.

The project is native Swift. It does not use Electron, Node.js, Python, a kernel extension, a launch agent, or an auto-updater.
106 changes: 106 additions & 0 deletions docs/PUBLIC_RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Public release guide

This guide prepares mx3-lite for public distribution without changing the
worker's runtime behavior. Release work does not add automatic startup,
telemetry, an updater, network access, or background logging.

## Owner-controlled prerequisites

Complete these before publishing the first release:

1. Choose and add a `LICENSE`. No license is selected automatically because
the repository owner must decide how others may use and redistribute the
source.
2. Confirm the repository is public and set a concise description and topics.
Recommended topics: `macos`, `swift`, `logitech`, `mx-master`, `mouse`, and
`accessibility`.
3. Obtain an Apple Developer Program membership, a **Developer ID
Application** certificate, and notarization credentials.
4. Confirm the release page and every asset can be opened in a private browser
window while signed out of GitHub.

Do not publish ad-hoc signed validation artifacts as official downloads.

## Build validation archives

The default command builds separate Apple Silicon and Intel archives. Separate
artifacts avoid installing an unused architecture slice and keep disk usage
lower than a universal binary.

```bash
bash scripts/package-release.sh
```

Build one architecture while iterating:

```bash
bash scripts/package-release.sh --arch arm64
bash scripts/package-release.sh --arch x86_64
```

Artifacts and `CHECKSUMS.txt` are written under:

```text
dist/releases/v<VERSION>/
```

Validation archives are ad-hoc signed. They verify compilation, exact
architecture, bundle structure, extraction, signatures, checksums, privacy
exclusions, and absence of test-framework linkage.

## Sign and notarize

List available signing identities:

```bash
security find-identity -v -p codesigning
```

Store notarization credentials in the login keychain once. Replace the sample
values with the owner's Apple account, team ID, and app-specific password:

```bash
xcrun notarytool store-credentials mx3-lite-notary \
--apple-id "APPLE_ID" \
--team-id "TEAM_ID" \
--password "APP_SPECIFIC_PASSWORD"
```

Create both public artifacts:

```bash
APP_VERSION=0.3.0 \
DEVELOPER_ID_APPLICATION="Developer ID Application: OWNER (TEAMID)" \
NOTARYTOOL_PROFILE="mx3-lite-notary" \
bash scripts/package-release.sh
```

The script signs the helper, settings executable, and app with hardened
runtime; submits each architecture archive to Apple; staples the accepted
ticket; rebuilds the ZIP; runs Gatekeeper validation; and writes final
checksums. Credentials are read from the keychain and are never copied into the
project or archive.

## Publish checklist

- [ ] A deliberate `LICENSE` is present
- [ ] Repository description, topics, and visibility are correct
- [ ] Both archives are Developer ID signed, notarized, and stapled
- [ ] `CHECKSUMS.txt` verifies with `shasum -a 256 -c CHECKSUMS.txt`
- [ ] `arm64` archive is tested on an Apple Silicon Mac
- [ ] `x86_64` archive is tested on an Intel Mac
- [ ] Fresh extraction and first launch pass on each architecture
- [ ] Accessibility and Input Monitoring permission flow is clear
- [ ] Start, quit settings, Status, Stop, and stale-PID behavior pass
- [ ] `Stop` leaves no `mx3-lite` process
- [ ] No network socket or normal log appears while the worker is running
- [ ] Release assets work in a signed-out private browser window
- [ ] Source and app archives contain no user config, personal path, build
cache, certificate, key, or notarization credential

## Runtime and architecture

The helper and settings app remain separate native Swift executables. Each
published ZIP contains only one architecture, so only that architecture is
installed and loaded. Packaging and notarization do not add runtime threads,
timers, wakeups, or network behavior to the worker.
2 changes: 1 addition & 1 deletion docs/performance/performance-release-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ until their isolated reports are complete.
| [#24](https://github.com/KulmanD/Mx-Master-3-For-mac-lightweight-swiftBased/issues/24) | `perf/reset-scroll-smoothing-state` | `reset-scroll-smoothing-state.md` | pending | pending |
| [#25](https://github.com/KulmanD/Mx-Master-3-For-mac-lightweight-swiftBased/issues/25) | `fix/preserve-scroll-delta-fields` | `preserve-scroll-delta-fields.md` | pending | pending |
| [#26](https://github.com/KulmanD/Mx-Master-3-For-mac-lightweight-swiftBased/issues/26) | `refactor/standardize-config-defaults` | `standardize-config-defaults.md` | accept | not applicable |
| [#27](https://github.com/KulmanD/Mx-Master-3-For-mac-lightweight-swiftBased/issues/27) | `release/public-distribution-readiness` | `public-distribution-readiness.md` | pending | pending |
| [#27](https://github.com/KulmanD/Mx-Master-3-For-mac-lightweight-swiftBased/issues/27) | `release/public-distribution-readiness` | `public-distribution-readiness.md` | accept | not applicable |

Valid isolated states are `pending`, `accept`, `redesign`, and `reject`.

Expand Down
144 changes: 144 additions & 0 deletions docs/performance/public-distribution-readiness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Performance report: public distribution readiness

Tracking issue: [#27](https://github.com/KulmanD/Mx-Master-3-For-mac-lightweight-swiftBased/issues/27)

## Decision

- Status: accept the distribution implementation. Public visibility, license
selection, Developer ID signing, notarization, and hardware launch tests
remain explicit owner-controlled publication gates.
- Functional benefit: maintainers can produce independently verified Apple
Silicon and Intel app archives, or use the same path with real Developer ID
and notarization credentials for public releases.
- Measured cost: zero worker runtime code. `Sources/`, `Package.swift`, and
`app/Info.plist` are identical to the accepted config-defaults branch.
- Reason: all new work is in release scripts, CI, and documentation. Separate
single-architecture archives avoid the installed-size cost of a universal
binary.

## Revisions

| Role | Branch | Commit | Dirty |
| --- | --- | --- | --- |
| Runtime base | `refactor/standardize-config-defaults` | `8ac3154774e0e17a28f9830c6de4336fbc964468` | no |
| Candidate | `release/public-distribution-readiness` | `7189fa5` | no |

This branch is stacked on #26 because public archives must use the neutral
`config/defaults.json` resource. A tree comparison confirms no runtime source,
package target, or bundle metadata changes after that base.

## Environment

| Item | Value |
| --- | --- |
| Mac | MacBook Pro `Mac16,8` |
| Host architecture | `arm64` |
| macOS | 26.5.1 (25F80) |
| Swift | Apple Swift 6.3.2, Swift 5.9 package mode |
| Local signing | ad-hoc validation only |
| Config SHA-256 | `b25df5cf7b6d9baa14a32efb3ddd883eefa0820ba2e9dbd2218e575c32d1f75a` |

The user config remained byte-for-byte unchanged. No Apple certificate or
notarization credential was available or fabricated.

## Implementation shape

- `scripts/build-release-app.sh` cross-compiles one requested architecture and
signs the helper, settings executable, and app bundle.
- `scripts/package-release.sh` builds separate `arm64` and `x86_64` archives,
optionally notarizes and staples them, and writes verified checksums.
- `scripts/verify-release.sh` extracts a fresh archive and checks bundle
structure, exact architecture, signatures, privacy exclusions, embedded
user paths, and test-framework linkage.
- CI builds both ad-hoc validation archives independently and uploads them as
workflow artifacts. It does not publish a GitHub release.
- `docs/PUBLIC_RELEASE.md` documents credentials, signed release commands,
Gatekeeper verification, publication checks, and owner-controlled blockers.
- The repository description and technical topics were populated. Visibility
remains private and no license was selected without owner approval.

## Static comparison

| Artifact | Architecture | Executable bytes | Archive bytes |
| --- | --- | ---: | ---: |
| Helper | arm64 | 432840 unsigned / 448496 signed | included below |
| Settings app | arm64 | 755824 unsigned / 769552 signed | 438956 |
| Helper | x86_64 | 442008 unsigned / 463744 signed | included below |
| Settings app | x86_64 | 746568 unsigned / 770704 signed | 450569 |

The Intel and Apple Silicon numbers are not runtime regressions: they are
different instruction sets and code-signature envelopes. Each installed app
contains exactly one architecture. The worker source is unchanged from #26,
and neither helper links `XCTest` or `Testing`.

The lightweight source archive was 81858 bytes and excluded `.build`, `.git`,
`.github`, `dist`, tests, performance probes, user config, retired
machine-specific defaults, and personal paths.

## Verification performed

- [x] `swift build`
- [x] `swift build -c release`
- [x] generated defaults check
- [x] shell syntax checks
- [x] workflow YAML parse
- [x] fresh arm64 archive extraction and verification
- [x] fresh x86_64 archive extraction and verification
- [x] exact architecture checks for both executables in both archives
- [x] nested ad-hoc signature verification
- [x] checksums generated and checked
- [x] source archive privacy/exclusion scan
- [x] Start, Status, Stop lifecycle; no remaining process after Stop
- [x] no worker runtime-source diff from #26
- [x] GitHub Actions: 34 tests plus arm64 and x86_64 archive jobs
- [ ] Developer ID signature and Apple notarization with owner credentials
- [ ] Gatekeeper first launch on a clean Apple Silicon Mac
- [ ] Gatekeeper first launch on a clean Intel Mac
- [ ] signed-out release-link verification after publication

Local `swift test` remains unavailable because the selected standalone Command
Line Tools installation does not provide XCTest. GitHub Actions supplies unit
tests and cross-architecture packaging checks.

## Runtime performance

Strict idle and active-event benchmarks were not repeated because the
candidate has no diff under `Sources/`, `Package.swift`, or `app/Info.plist`.
Running the same executable again would measure machine noise rather than a
distribution change. The lifecycle smoke test still stopped the worker
completely, and the existing #26 measurement remains applicable.

## Safety checks

- [x] No worker polling loop, timer, thread, or event-tap change
- [x] No automatic startup, launch agent, or login item
- [x] No updater, telemetry, or app runtime network code
- [x] No normal background logging
- [x] No credentials stored in source or artifacts
- [x] Validation archives clearly identify ad-hoc signing
- [x] Notarization fails closed without a Developer ID identity
- [x] Release checks modify no user config
- [x] Separate architecture archives avoid universal-binary disk overhead

Notarization itself contacts Apple only when a maintainer explicitly provides a
keychain profile to the release script. That deployment action is not linked
into or callable by the app.

## Publication gates

The engineering branch is ready, but an official public release must not be
claimed until the owner:

1. chooses a license or deliberately documents proprietary source terms;
2. changes repository visibility from private when ready;
3. supplies a Developer ID Application identity and notarization profile;
4. completes signed first-launch tests on real Apple Silicon and Intel Macs;
5. verifies release links while signed out of GitHub.

## Interaction result

| Combination | Base | Candidate | Result |
| --- | --- | --- | --- |
| Runtime features | #26 accepted tree | `7189fa5` | no runtime diff |

No runtime interaction branch is required for distribution-only files.
Loading
Loading