Skip to content
Merged
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: 17 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

steps:
- name: Check out source
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Show toolchain
run: |
Expand Down Expand Up @@ -59,3 +59,19 @@ jobs:

- name: Verify release bundle
run: ./Scripts/verify-unsigned-build.sh build/DerivedData/Build/Products/Release/Startle.app

- name: Package unsigned app
run: |
mkdir -p build/artifacts
ditto \
-c -k --sequesterRsrc --keepParent \
build/DerivedData/Build/Products/Release/Startle.app \
build/artifacts/Startle-macOS-unsigned.zip

- name: Upload unsigned app
uses: actions/upload-artifact@v7
with:
name: Startle-${{ github.sha }}-macOS-unsigned
path: build/artifacts/Startle-macOS-unsigned.zip
if-no-files-found: error
retention-days: 14
106 changes: 106 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Release

on:
push:
tags:
- "v[0-9]*"

permissions:
contents: write

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
name: Build unsigned release
runs-on: macos-15
timeout-minutes: 25

steps:
- name: Check out source
uses: actions/checkout@v7

- name: Show toolchain
run: |
xcodebuild -version
swift --version
gh --version

- name: Validate source
run: |
plutil -lint Config/Info.plist
plutil -lint Config/Startle.entitlements
plutil -lint Sources/StartleApp/Resources/PrivacyInfo.xcprivacy
xcrun swift-format lint --recursive --strict Sources Tests Package.swift

- name: Run release-mode package tests
run: swift test -c release -Xswiftc -warnings-as-errors

- name: Run Xcode tests
run: |
xcodebuild \
-project Startle.xcodeproj \
-scheme Startle \
-destination 'platform=macOS' \
CODE_SIGNING_ALLOWED=NO \
ONLY_ACTIVE_ARCH=YES \
SWIFT_TREAT_WARNINGS_AS_ERRORS=YES \
test

- name: Build universal release app
run: |
release_version=${GITHUB_REF_NAME#v}
xcodebuild \
-project Startle.xcodeproj \
-scheme Startle \
-configuration Release \
-destination 'generic/platform=macOS' \
-derivedDataPath build/DerivedData \
CODE_SIGNING_ALLOWED=NO \
MARKETING_VERSION="$release_version" \
CURRENT_PROJECT_VERSION="$GITHUB_RUN_NUMBER" \
SWIFT_TREAT_WARNINGS_AS_ERRORS=YES \
build

- name: Verify unsigned bundle
run: ./Scripts/verify-unsigned-build.sh build/DerivedData/Build/Products/Release/Startle.app

- name: Package release assets
run: |
release_version=${GITHUB_REF_NAME#v}
archive_name="Startle-${release_version}-macOS-unsigned.zip"
mkdir -p dist
ditto \
-c -k --sequesterRsrc --keepParent \
build/DerivedData/Build/Products/Release/Startle.app \
"dist/$archive_name"
cd dist
shasum -a 256 "$archive_name" > "$archive_name.sha256"

- name: Publish prerelease
env:
GH_TOKEN: ${{ github.token }}
run: |
release_version=${GITHUB_REF_NAME#v}
archive_name="Startle-${release_version}-macOS-unsigned.zip"
release_notes="Unsigned preview build. macOS will identify this app as coming from an unidentified developer. Review the installation notes in the repository before opening it."

if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release upload \
"$GITHUB_REF_NAME" \
"dist/$archive_name" \
"dist/$archive_name.sha256" \
--clobber
else
gh release create \
"$GITHUB_REF_NAME" \
"dist/$archive_name" \
"dist/$archive_name.sha256" \
--verify-tag \
--prerelease \
--title "Startle $GITHUB_REF_NAME (unsigned)" \
--notes "$release_notes" \
--generate-notes
fi
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Startle is a native macOS prank/productivity utility that waits in the menu bar

[![CI](https://github.com/Charlie284/Startle/actions/workflows/ci.yml/badge.svg)](https://github.com/Charlie284/Startle/actions/workflows/ci.yml)

**Status:** Pre-release. Source builds are available for development and testing. A production download should be published only after completing the signing, notarization, and packaged-app checks in [RELEASE.md](RELEASE.md).
**Status:** Pre-release. Source builds and unsigned preview downloads are available for development and testing. A trusted production download should be published only after completing the signing, notarization, and packaged-app checks in [RELEASE.md](RELEASE.md).

> **Safety:** Do not use Startle on anyone with a heart condition, epilepsy, severe anxiety, PTSD, or sound sensitivity. Obtain clear consent. Never use it where a sudden reaction could cause injury.

Expand Down Expand Up @@ -40,6 +40,12 @@ xcodebuild \

The Xcode target enables App Sandbox and Hardened Runtime. Distribution still requires an Apple Developer signing identity and notarization; project builds and unsigned CI artifacts are not substitutes for those checks.

## Preview downloads

Version tags matching `v*` build a universal macOS app and publish it as a GitHub prerelease with a SHA-256 checksum. These downloads are unsigned because the project does not currently use a paid Apple Developer identity.

macOS will identify an unsigned download as coming from an unidentified developer. After verifying the checksum, extract the ZIP and use **Control-click → Open** for the first launch. Do not treat an unsigned preview as equivalent to a Developer ID-signed and notarized release.

## Architecture

- `SettingsStore` persists simple and structured preferences through Codable data in UserDefaults.
Expand Down Expand Up @@ -95,6 +101,7 @@ swift test
- [CONTRIBUTING.md](CONTRIBUTING.md) explains development and pull-request expectations.
- [SECURITY.md](SECURITY.md) explains responsible vulnerability reporting.
- [RELEASE.md](RELEASE.md) defines the signed and notarized release gate.
- `.github/workflows/release.yml` publishes unsigned prereleases from version tags.
- [CHANGELOG.md](CHANGELOG.md) tracks user-visible changes.

Startle is available under the [MIT License](LICENSE).
6 changes: 6 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Startle is not ready to distribute merely because `swift test` passes. A release must also be archived, signed, notarized, and exercised as the packaged sandboxed app.

## Unsigned preview releases

Pushing a version tag such as `v1.0.0` runs `.github/workflows/release.yml`. The workflow repeats the source and Xcode tests, builds a universal macOS app, verifies the unsigned bundle, and publishes a prerelease ZIP with its SHA-256 checksum.

Unsigned previews are intended for development and evaluation. Gatekeeper will identify them as coming from an unidentified developer, and they do not satisfy the production release process below.

## 1. Prepare the version

1. Start from a clean default branch with passing CI.
Expand Down