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
82 changes: 82 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: CI

on:
push:
branches: ["main", "agent/**"]
pull_request:

permissions:
contents: read

jobs:
build-and-unit-test:
name: Build and unit test (${{ matrix.runner }})
strategy:
fail-fast: false
matrix:
include:
- runner: macos-26
arch: arm64
- runner: macos-26-intel
arch: x86_64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v5
- name: Toolchain
run: xcodebuild -version
- name: Build
run: |
set -o pipefail
xcodebuild \
-project LaunchdTOC.xcodeproj \
-scheme LaunchdTOC \
-configuration Debug \
-destination "platform=macOS,arch=${{ matrix.arch }}" \
CODE_SIGNING_ALLOWED=NO \
build
- name: Unit tests
run: |
set -o pipefail
xcodebuild \
-project LaunchdTOC.xcodeproj \
-scheme LaunchdTOC \
-configuration Debug \
-destination "platform=macOS,arch=${{ matrix.arch }}" \
CODE_SIGNING_ALLOWED=NO \
-only-testing:LaunchdTOCTests \
test

analyze-and-verify:
name: Analyze and verify universal product
runs-on: macos-26
steps:
- uses: actions/checkout@v5
- name: Static analysis
run: |
xcodebuild \
-project LaunchdTOC.xcodeproj \
-scheme LaunchdTOC \
-configuration Debug \
-destination "platform=macOS" \
CODE_SIGNING_ALLOWED=NO \
analyze
- name: Universal Release build
run: |
xcodebuild \
-project LaunchdTOC.xcodeproj \
-scheme LaunchdTOC \
-configuration Release \
-derivedDataPath build/DerivedData \
-destination "generic/platform=macOS" \
ARCHS="arm64 x86_64" \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGNING_ALLOWED=NO \
build
- name: Verify architectures and entitlements
run: |
app_path="build/DerivedData/Build/Products/Release/Launchd TOC.app"
binary_path="$app_path/Contents/MacOS/Launchd TOC"
architectures="$(lipo -archs "$binary_path")"
[[ "$architectures" == *"arm64"* && "$architectures" == *"x86_64"* ]]
entitlements="$(codesign -d --entitlements - "$app_path" 2>&1 || true)"
[[ "$entitlements" != *"com.apple.security.app-sandbox"* ]]
142 changes: 142 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: Notarized Release

on:
push:
tags: ["v*"]

permissions:
contents: write

jobs:
release:
runs-on: macos-26
steps:
- uses: actions/checkout@v5

- name: Verify tag and project version
env:
TAG_NAME: ${{ github.ref_name }}
run: |
tag_version="${TAG_NAME#v}"
tag_core="${tag_version%%-*}"
project_version="$(xcodebuild -project LaunchdTOC.xcodeproj -scheme LaunchdTOC -showBuildSettings | awk '/MARKETING_VERSION/ { print $3; exit }')"
[[ "$tag_core" == "$project_version" ]]
echo "VERSION=$tag_version" >> "$GITHUB_ENV"

- name: Create ephemeral signing keychain
env:
CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_APPLICATION_P12_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.DEVELOPER_ID_APPLICATION_P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
certificate_path="$RUNNER_TEMP/developer-id.p12"
keychain_path="$RUNNER_TEMP/launchd-toc.keychain-db"
printf '%s' "$CERTIFICATE_BASE64" | base64 --decode > "$certificate_path"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
security set-keychain-settings -lut 21600 "$keychain_path"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
security import "$certificate_path" \
-P "$CERTIFICATE_PASSWORD" \
-A \
-t cert \
-f pkcs12 \
-k "$keychain_path"
security list-keychain -d user -s "$keychain_path"
security set-key-partition-list \
-S apple-tool:,apple:,codesign: \
-s \
-k "$KEYCHAIN_PASSWORD" \
"$keychain_path"
echo "SIGNING_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"

- name: Archive universal signed application
run: |
xcodebuild archive \
-project LaunchdTOC.xcodeproj \
-scheme LaunchdTOC \
-configuration Release \
-archivePath build/LaunchdTOC.xcarchive \
-destination "generic/platform=macOS" \
ARCHS="arm64 x86_64" \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGN_STYLE=Manual \
DEVELOPMENT_TEAM=N9U29A4T8J \
CODE_SIGN_IDENTITY="Developer ID Application"

- name: Verify signed product
run: |
app_path="build/LaunchdTOC.xcarchive/Products/Applications/Launchd TOC.app"
scripts/verify_app.sh "$app_path"
codesign -dvvv "$app_path"

- name: Package DMG
run: |
app_path="build/LaunchdTOC.xcarchive/Products/Applications/Launchd TOC.app"
scripts/package_dmg.sh "$app_path" "$VERSION" build/release
codesign --force \
--sign "Developer ID Application" \
--timestamp \
"build/release/Launchd-TOC-${VERSION}-universal.dmg"

- name: Notarize and staple
env:
NOTARY_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY_BASE64 }}
NOTARY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
NOTARY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
run: |
key_path="$RUNNER_TEMP/AuthKey_${NOTARY_KEY_ID}.p8"
printf '%s' "$NOTARY_KEY_BASE64" | base64 --decode > "$key_path"
echo "NOTARY_KEY_PATH=$key_path" >> "$GITHUB_ENV"
dmg_path="build/release/Launchd-TOC-${VERSION}-universal.dmg"
xcrun notarytool submit "$dmg_path" \
--key "$key_path" \
--key-id "$NOTARY_KEY_ID" \
--issuer "$NOTARY_ISSUER_ID" \
--wait
xcrun stapler staple "$dmg_path"
xcrun stapler validate "$dmg_path"

- name: Gatekeeper and clean-install smoke checks
run: |
dmg_path="build/release/Launchd-TOC-${VERSION}-universal.dmg"
spctl --assess --type open --context context:primary-signature --verbose=2 "$dmg_path"
mount_point="$(mktemp -d "$RUNNER_TEMP/launchd-toc-mount.XXXXXX")"
hdiutil attach "$dmg_path" -nobrowse -readonly -mountpoint "$mount_point"
installed_path="$RUNNER_TEMP/Clean Install/Launchd TOC.app"
mkdir -p "$(dirname "$installed_path")"
ditto "$mount_point/Launchd TOC.app" "$installed_path"
hdiutil detach "$mount_point"
spctl --assess --type execute --verbose=2 "$installed_path"
scripts/verify_app.sh "$installed_path"

- name: Generate checksum
run: |
cd build/release
shasum -a 256 "Launchd-TOC-${VERSION}-universal.dmg" \
> "Launchd-TOC-${VERSION}-universal.dmg.sha256"

- name: Publish GitHub release
env:
GH_TOKEN: ${{ github.token }}
TAG_NAME: ${{ github.ref_name }}
run: |
prerelease_args=()
if [[ "$TAG_NAME" == *"-"* ]]; then
prerelease_args+=(--prerelease)
fi
gh release create "$TAG_NAME" \
"build/release/Launchd-TOC-${VERSION}-universal.dmg" \
"build/release/Launchd-TOC-${VERSION}-universal.dmg.sha256" \
--generate-notes \
--verify-tag \
"${prerelease_args[@]}"

- name: Remove signing material
if: always()
run: |
if [[ -n "${SIGNING_KEYCHAIN:-}" ]]; then
security delete-keychain "$SIGNING_KEYCHAIN" || true
fi
if [[ -n "${NOTARY_KEY_PATH:-}" ]]; then
rm -f "$NOTARY_KEY_PATH"
fi
19 changes: 19 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Contributing

Thank you for helping improve Launchd TOC.

## Before opening a pull request

1. Keep the project native Swift/SwiftUI with no third-party runtime dependencies.
2. Preserve the user-only mutation boundary and fixed-executable process model.
3. Add or update tests for every filesystem, parsing, or launchctl behavior change.
4. Run the unit and UI suites on macOS 26 with Xcode 26.3 or later.
5. Treat warnings as errors and retain Swift 6 strict concurrency.
6. Do not add analytics, automatic network requests, administrator access, or an undocumented launchd option.
7. Do not copy code, assets, UI text, or icons from `azu/launchd-ui` or other applications.

Use clear commits and explain user-visible safety implications in the pull request.

## Rights

This repository does not include an open-source license. Copyright © 2026 Thierry Charbonnel. All rights reserved. A contribution does not change that status unless the copyright holder agrees separately in writing.
Loading