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
125 changes: 122 additions & 3 deletions .github/workflows/cleanzip-liquid-glass-icon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
release_tag:
description: "Release tag to update"
required: true
default: "v2.6.34"
default: "v2.6.35"
upload_release:
description: "Upload pkg, zip, and checksums to the release"
required: true
Expand Down Expand Up @@ -122,17 +122,136 @@ jobs:
print("Assets.car contains a dynamic app icon image stack.")
PY

- name: Configure Developer ID signing
env:
APPLICATION_P12_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_P12_BASE64 }}
APPLICATION_P12_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_P12_PASSWORD }}
APPLICATION_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_IDENTITY }}
INSTALLER_P12_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_P12_BASE64 }}
INSTALLER_P12_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_P12_PASSWORD }}
INSTALLER_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_IDENTITY }}
shell: bash
run: |
set -euo pipefail
if [[ -z "$APPLICATION_P12_BASE64" || -z "$APPLICATION_P12_PASSWORD" || -z "$APPLICATION_IDENTITY" || \
-z "$INSTALLER_P12_BASE64" || -z "$INSTALLER_P12_PASSWORD" || -z "$INSTALLER_IDENTITY" ]]; then
echo "CLEANZIP_SIGNING_MODE=ad-hoc" >> "$GITHUB_ENV"
echo "::notice::Developer ID secrets are incomplete; this build will be ad-hoc signed and cannot be notarized."
exit 0
fi

keychain="$RUNNER_TEMP/cleanzip-signing.keychain-db"
keychain_password="$(openssl rand -hex 24)"
application_p12="$RUNNER_TEMP/developer-id-application.p12"
installer_p12="$RUNNER_TEMP/developer-id-installer.p12"
printf '%s' "$APPLICATION_P12_BASE64" | /usr/bin/base64 -D > "$application_p12"
printf '%s' "$INSTALLER_P12_BASE64" | /usr/bin/base64 -D > "$installer_p12"

security create-keychain -p "$keychain_password" "$keychain"
security set-keychain-settings -lut 21600 "$keychain"
security unlock-keychain -p "$keychain_password" "$keychain"
security import "$application_p12" -k "$keychain" -P "$APPLICATION_P12_PASSWORD" -T /usr/bin/codesign
security import "$installer_p12" -k "$keychain" -P "$INSTALLER_P12_PASSWORD" -T /usr/bin/productbuild -T /usr/bin/pkgbuild
security set-key-partition-list -S apple-tool:,apple: -s -k "$keychain_password" "$keychain"
security list-keychains -d user -s "$keychain"

echo "CLEANZIP_SIGNING_MODE=developer-id" >> "$GITHUB_ENV"
echo "CLEANZIP_SIGNING_KEYCHAIN=$keychain" >> "$GITHUB_ENV"
echo "CLEANZIP_APPLICATION_IDENTITY=$APPLICATION_IDENTITY" >> "$GITHUB_ENV"
echo "CLEANZIP_INSTALLER_IDENTITY=$INSTALLER_IDENTITY" >> "$GITHUB_ENV"

- name: Sign app bundles
shell: bash
run: |
set -euo pipefail
codesign --force --deep --sign - work/CleanZipBuild/CleanZip.app
codesign --force --deep --sign - work/CleanZipBuild/CleanZipService.service
sign_bundle() {
local bundle="$1"
local helper="$bundle/Contents/Resources/7zz"
if [[ "$CLEANZIP_SIGNING_MODE" == "developer-id" ]]; then
codesign --force --options runtime --timestamp --keychain "$CLEANZIP_SIGNING_KEYCHAIN" --sign "$CLEANZIP_APPLICATION_IDENTITY" "$helper"
codesign --force --options runtime --timestamp --keychain "$CLEANZIP_SIGNING_KEYCHAIN" --sign "$CLEANZIP_APPLICATION_IDENTITY" "$bundle"
else
codesign --force --options runtime --timestamp=none --sign - "$helper"
codesign --force --options runtime --timestamp=none --sign - "$bundle"
fi
codesign --verify --deep --strict --verbose=2 "$bundle"
}
sign_bundle work/CleanZipBuild/CleanZip.app
sign_bundle work/CleanZipBuild/CleanZipService.service

- name: Notarize and staple app bundles
env:
NOTARY_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_KEY_P8_BASE64 }}
NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
shell: bash
run: |
set -euo pipefail
if [[ "$CLEANZIP_SIGNING_MODE" != "developer-id" || -z "$NOTARY_KEY_P8_BASE64" || -z "$NOTARY_KEY_ID" || -z "$NOTARY_ISSUER_ID" ]]; then
echo "CLEANZIP_NOTARIZED=0" >> "$GITHUB_ENV"
echo "::notice::Notary credentials are unavailable; app bundles were not notarized."
exit 0
fi

notary_key="$RUNNER_TEMP/AuthKey.p8"
notary_stage="$RUNNER_TEMP/cleanzip-notary"
notary_zip="$RUNNER_TEMP/CleanZip-notary-upload.zip"
printf '%s' "$NOTARY_KEY_P8_BASE64" | /usr/bin/base64 -D > "$notary_key"
mkdir -p "$notary_stage"
ditto work/CleanZipBuild/CleanZip.app "$notary_stage/CleanZip.app"
ditto work/CleanZipBuild/CleanZipService.service "$notary_stage/CleanZipService.service"
ditto -c -k --keepParent "$notary_stage" "$notary_zip"
xcrun notarytool submit "$notary_zip" --key "$notary_key" --key-id "$NOTARY_KEY_ID" --issuer "$NOTARY_ISSUER_ID" --wait
xcrun stapler staple work/CleanZipBuild/CleanZip.app
xcrun stapler validate work/CleanZipBuild/CleanZip.app
xcrun stapler staple work/CleanZipBuild/CleanZipService.service || echo "::notice::The service ticket remains available online."
spctl --assess --type execute --verbose=2 work/CleanZipBuild/CleanZip.app
echo "CLEANZIP_NOTARIZED=1" >> "$GITHUB_ENV"

- name: Build installer package
shell: bash
run: work/CleanZipBuild/src/package.sh

- name: Notarize and staple installer package
if: ${{ env.CLEANZIP_NOTARIZED == '1' }}
env:
NOTARY_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_KEY_P8_BASE64 }}
NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
shell: bash
run: |
set -euo pipefail
notary_key="$RUNNER_TEMP/AuthKey.p8"
pkg="$(find work/CleanZipBuild/dist -maxdepth 1 -name 'CleanZip-*.pkg' -print | sort | tail -n 1)"
test -f "$pkg"
printf '%s' "$NOTARY_KEY_P8_BASE64" | /usr/bin/base64 -D > "$notary_key"
xcrun notarytool submit "$pkg" --key "$notary_key" --key-id "$NOTARY_KEY_ID" --issuer "$NOTARY_ISSUER_ID" --wait
xcrun stapler staple "$pkg"
xcrun stapler validate "$pkg"
pkgutil --check-signature "$pkg"
spctl --assess --type install --verbose=2 "$pkg"

- name: Summarize distribution trust
shell: bash
run: |
{
echo "### CleanZip distribution"
echo ""
echo "- Signing mode: \`$CLEANZIP_SIGNING_MODE\`"
echo "- Notarized: \`${CLEANZIP_NOTARIZED:-0}\`"
} >> "$GITHUB_STEP_SUMMARY"

- name: Refresh release checksums
shell: bash
run: |
set -euo pipefail
cd work/CleanZipBuild/dist
pkg="$(find . -maxdepth 1 -name 'CleanZip-*.pkg' -print | sort | tail -n 1)"
zip="$(find . -maxdepth 1 -name 'CleanZip-*.zip' -print | sort | tail -n 1)"
test -f "$pkg"
test -f "$zip"
shasum -a 256 "${pkg#./}" "${zip#./}" > SHA256SUMS.txt

- name: Upload package assets to release
if: ${{ github.event_name == 'workflow_dispatch' && inputs.upload_release }}
env:
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
·
<a href="https://lyc280705.github.io/CleanZip/">Product page</a>
·
<a href="https://github.com/lyc280705/CleanZip/releases/tag/v2.6.34">CleanZip 2.6.34</a>
<a href="https://github.com/lyc280705/CleanZip/releases/tag/v2.6.35">CleanZip 2.6.35</a>
·
<a href="#build-from-source">Build from source</a>
</p>
Expand Down Expand Up @@ -59,7 +59,7 @@ It is intentionally small: no always-on background app, no history database, no

## Download

Download `CleanZip-2.6.34.pkg` from the [latest release](https://github.com/lyc280705/CleanZip/releases/latest). For most users, the `.pkg` installer is the easiest option.
Download `CleanZip-2.6.35.pkg` from the [latest release](https://github.com/lyc280705/CleanZip/releases/latest). For most users, the `.pkg` installer is the easiest option.

The installer places:

Expand All @@ -70,9 +70,9 @@ CleanZip is ad-hoc signed for open source distribution, but it is not notarized

For the `.pkg` installer:

1. In Finder, Control-click `CleanZip-2.6.34.pkg` and choose **Open**.
1. In Finder, Control-click `CleanZip-2.6.35.pkg` and choose **Open**.
2. If the same warning still appears with only **Done** and **Move to Trash**, open **System Settings** -> **Privacy & Security**.
3. At the bottom of Privacy & Security, choose **Open Anyway** for `CleanZip-2.6.34.pkg`, then confirm.
3. At the bottom of Privacy & Security, choose **Open Anyway** for `CleanZip-2.6.35.pkg`, then confirm.

After installation, if macOS blocks `CleanZip.app` itself, Control-click `CleanZip.app` in `/Applications` and choose **Open**. If it is still blocked, use **System Settings** -> **Privacy & Security** -> **Open Anyway** for `CleanZip.app`.

Expand All @@ -81,11 +81,11 @@ After you approve the installer or app once, macOS opens it normally.
Advanced terminal alternative for the downloaded package:

```bash
xattr -dr com.apple.quarantine ~/Downloads/CleanZip-2.6.34.pkg
open ~/Downloads/CleanZip-2.6.34.pkg
xattr -dr com.apple.quarantine ~/Downloads/CleanZip-2.6.35.pkg
open ~/Downloads/CleanZip-2.6.35.pkg
```

Manual installation is also available from `CleanZip-2.6.34.zip`: move `CleanZip.app` to `/Applications` and `CleanZipService.service` to `/Library/Services`.
Manual installation is also available from `CleanZip-2.6.35.zip`: move `CleanZip.app` to `/Applications` and `CleanZipService.service` to `/Library/Services`.

## Compatibility

Expand All @@ -100,9 +100,10 @@ On macOS 26, CleanZip uses Liquid Glass interface effects where available. On ma
| Clean compression | Creates clean ZIP output and excludes `.DS_Store`, `__MACOSX/`, and `._*` metadata. |
| Finder integration | Adds one right-click service for compressing ordinary files/folders or extracting archives. |
| Archive preview | Lists archive contents with name, size, modified time, and folder structure. Includes search. |
| Encrypted archives | Requests passwords in a native secure field; passwords are sent to `7zz` through standard input and never placed in process arguments. |
| Extraction | Extracts common formats through bundled `7zz` and system tools. |
| Split archive creation | Supports split ZIP and split 7Z creation with common presets and custom sizes. |
| Progress | Shows progress for larger compression and extraction jobs in the app and lightweight service HUD. |
| Progress | Shows cancellable progress for larger compression and extraction jobs in the app and lightweight service HUD, then removes partial output after cancellation. |
| Compatibility | Universal app for supported Intel and Apple Silicon Macs running macOS 14 or later. |
| Localization | Localized app UI, Finder service menu, notifications, errors, and document metadata. |

Expand Down Expand Up @@ -151,6 +152,7 @@ CleanZip runs locally on your Mac. Archive operations are performed with local s
- `work/CleanZipBuild/src/build_xcode.sh`: Xcode release build script for the app and Finder service.
- `work/CleanZipBuild/src/build.sh`: lightweight local Swift build fallback for Macs without full Xcode.
- `work/CleanZipBuild/src/package.sh`: package and ZIP release artifact script.
- `docs/RELEASING.md`: optional Developer ID signing and Apple notarization setup for maintainers.
- `work/CleanZipBuild/src/generate_filled_icon.py`: vector icon generator and `Assets.car` compiler when Xcode `actool` is available.
- `.github/workflows/cleanzip-liquid-glass-icon.yml`: macOS 26 GitHub Actions release build that uses full Xcode, compiles the dynamic icon stack, packages CleanZip, and can update release assets.

Expand All @@ -172,7 +174,7 @@ gh workflow run cleanzip-liquid-glass-icon.yml --repo lyc280705/CleanZip --ref m
To rebuild and update an existing GitHub release asset set:

```sh
gh workflow run cleanzip-liquid-glass-icon.yml --repo lyc280705/CleanZip --ref main -f release_tag=v2.6.34 -f upload_release=true
gh workflow run cleanzip-liquid-glass-icon.yml --repo lyc280705/CleanZip --ref main -f release_tag=v2.6.35 -f upload_release=true
```

Lightweight local fallback with Command Line Tools:
Expand All @@ -181,7 +183,7 @@ Lightweight local fallback with Command Line Tools:
work/CleanZipBuild/src/build.sh
```

Dynamic Liquid Glass icon compilation requires Xcode 26 `actool`. The lightweight fallback still creates a usable app bundle, but the GitHub Actions/Xcode path is the canonical release path.
Dynamic Liquid Glass icon compilation requires Xcode 26 `actool`. The lightweight fallback still creates a usable app bundle, but the GitHub Actions/Xcode path is the canonical release path. Both paths compile in Swift 6 language mode with complete concurrency checking; long-running archive work uses Swift 6.2 structured concurrency to stay off the main actor.

## License

Expand Down
46 changes: 46 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Releasing CleanZip

CleanZip's release workflow always builds universal `arm64` and `x86_64` app, service, package, and ZIP artifacts. It supports two trust modes:

- **Developer ID:** signs with the Hardened Runtime, submits the app bundles and installer to Apple's notary service, staples tickets, and verifies them with `codesign`, `stapler`, and `spctl`.
- **Ad-hoc fallback:** keeps source builds installable when credentials are unavailable, but Gatekeeper requires the manual approval steps documented in the README.

## GitHub Secrets

Add these Actions secrets in **Settings -> Secrets and variables -> Actions**:

| Secret | Value |
| --- | --- |
| `APPLE_DEVELOPER_ID_APPLICATION_P12_BASE64` | Base64-encoded Developer ID Application certificate and private key (`.p12`). |
| `APPLE_DEVELOPER_ID_APPLICATION_P12_PASSWORD` | Password used when exporting that `.p12`. |
| `APPLE_DEVELOPER_ID_APPLICATION_IDENTITY` | Full certificate name, such as `Developer ID Application: Example (TEAMID)`. |
| `APPLE_DEVELOPER_ID_INSTALLER_P12_BASE64` | Base64-encoded Developer ID Installer certificate and private key (`.p12`). |
| `APPLE_DEVELOPER_ID_INSTALLER_P12_PASSWORD` | Password used when exporting that `.p12`. |
| `APPLE_DEVELOPER_ID_INSTALLER_IDENTITY` | Full certificate name, such as `Developer ID Installer: Example (TEAMID)`. |
| `APPLE_NOTARY_KEY_P8_BASE64` | Base64-encoded App Store Connect API private key (`AuthKey_XXXXXXXXXX.p8`). |
| `APPLE_NOTARY_KEY_ID` | App Store Connect API key ID. |
| `APPLE_NOTARY_ISSUER_ID` | App Store Connect API issuer ID. |

Encode certificate and API key files without line wrapping:

```sh
base64 -i DeveloperIDApplication.p12 | pbcopy
base64 -i DeveloperIDInstaller.p12 | pbcopy
base64 -i AuthKey_XXXXXXXXXX.p8 | pbcopy
```

The workflow imports certificates into an ephemeral runner keychain. It never writes certificate passwords or private-key contents to logs or release artifacts.

## Publish

Create the tag and release first, then dispatch the workflow so its artifacts replace the release assets:

```sh
gh workflow run cleanzip-liquid-glass-icon.yml \
--repo lyc280705/CleanZip \
--ref main \
-f release_tag=v2.6.35 \
-f upload_release=true
```

Check the workflow summary before announcing the release. A public build should only be described as notarized when the summary reports `developer-id` and `1` for the signing and notarization fields.
12 changes: 6 additions & 6 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"url": "https://lyc280705.github.io/CleanZip/",
"image": "https://lyc280705.github.io/CleanZip/images/app-icon.png",
"downloadUrl": "https://github.com/lyc280705/CleanZip/releases/latest",
"softwareVersion": "2.6.30",
"softwareVersion": "2.6.35",
"license": "https://github.com/lyc280705/CleanZip/blob/main/LICENSE",
"codeRepository": "https://github.com/lyc280705/CleanZip",
"sameAs": [
Expand Down Expand Up @@ -82,7 +82,7 @@ <h1 id="hero-title">Clean ZIPs, archive previews, and one Finder action.</h1>
<a class="button primary" href="https://github.com/lyc280705/CleanZip/releases/latest">Download for macOS</a>
<a class="button secondary" href="https://github.com/lyc280705/CleanZip">View on GitHub</a>
</div>
<p class="release-note">Latest release: CleanZip 2.6.30. Universal app for Intel and Apple Silicon Macs.</p>
<p class="release-note">Latest release: CleanZip 2.6.35. Universal app for Intel and Apple Silicon Macs.</p>
</div>
</section>

Expand Down Expand Up @@ -185,7 +185,7 @@ <h2 id="formats-title">Preview and extract the archive formats people actually s
<h2 id="install-title">Download the package and start from Finder.</h2>
</div>
<ol>
<li>Download <a href="https://github.com/lyc280705/CleanZip/releases/latest">CleanZip-2.6.30.pkg</a>.</li>
<li>Download <a href="https://github.com/lyc280705/CleanZip/releases/latest">CleanZip-2.6.35.pkg</a>.</li>
<li>Open the installer. It installs CleanZip.app and the Finder service.</li>
<li>Select files, folders, or archives in Finder and choose CleanZip Compress or Extract.</li>
</ol>
Expand All @@ -197,11 +197,11 @@ <h2 id="install-title">Download the package and start from Finder.</h2>
<h2 id="warning-title">If macOS blocks the installer.</h2>
</div>
<div class="warning-copy">
<p>CleanZip is ad-hoc signed and open source, but it is not notarized with an Apple Developer ID. macOS may block <strong>CleanZip-2.6.30.pkg</strong> before the installer opens and show a warning such as “Apple could not verify CleanZip is free of malware.”</p>
<p>CleanZip is ad-hoc signed and open source, but it is not notarized with an Apple Developer ID. macOS may block <strong>CleanZip-2.6.35.pkg</strong> before the installer opens and show a warning such as “Apple could not verify CleanZip is free of malware.”</p>
<ol>
<li>In Finder, Control-click <strong>CleanZip-2.6.30.pkg</strong>, then choose <strong>Open</strong>.</li>
<li>In Finder, Control-click <strong>CleanZip-2.6.35.pkg</strong>, then choose <strong>Open</strong>.</li>
<li>If the same warning still appears with only <strong>Done</strong> and <strong>Move to Trash</strong>, open <strong>System Settings</strong> → <strong>Privacy &amp; Security</strong>.</li>
<li>At the bottom of Privacy &amp; Security, choose <strong>Open Anyway</strong> for <strong>CleanZip-2.6.30.pkg</strong>, then confirm.</li>
<li>At the bottom of Privacy &amp; Security, choose <strong>Open Anyway</strong> for <strong>CleanZip-2.6.35.pkg</strong>, then confirm.</li>
<li>After installation, if macOS blocks <strong>CleanZip.app</strong>, repeat the same <strong>Open Anyway</strong> step for the app.</li>
</ol>
<p>After you approve the installer or app once, macOS opens it normally.</p>
Expand Down
Loading
Loading