chore(release): prepare V1.0.2
This commit is contained in:
120
.github/workflows/release.yml
vendored
120
.github/workflows/release.yml
vendored
@@ -54,9 +54,102 @@ jobs:
|
||||
path: bin/*-darwin-*
|
||||
retention-days: 1
|
||||
|
||||
native:
|
||||
name: Build Native Release
|
||||
runs-on: macos-latest
|
||||
outputs:
|
||||
packaging_mode: ${{ steps.mode.outputs.packaging_mode }}
|
||||
prerelease: ${{ steps.mode.outputs.prerelease }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
|
||||
|
||||
- name: Derive native release version
|
||||
run: |
|
||||
echo "ATLAS_VERSION=${GITHUB_REF_NAME#V}" >> "$GITHUB_ENV"
|
||||
echo "ATLAS_BUILD_NUMBER=${GITHUB_RUN_NUMBER}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Select native packaging mode
|
||||
id: mode
|
||||
env:
|
||||
ATLAS_RELEASE_APP_CERT_P12_BASE64: ${{ secrets.ATLAS_RELEASE_APP_CERT_P12_BASE64 }}
|
||||
ATLAS_RELEASE_APP_CERT_P12_PASSWORD: ${{ secrets.ATLAS_RELEASE_APP_CERT_P12_PASSWORD }}
|
||||
ATLAS_RELEASE_INSTALLER_CERT_P12_BASE64: ${{ secrets.ATLAS_RELEASE_INSTALLER_CERT_P12_BASE64 }}
|
||||
ATLAS_RELEASE_INSTALLER_CERT_P12_PASSWORD: ${{ secrets.ATLAS_RELEASE_INSTALLER_CERT_P12_PASSWORD }}
|
||||
ATLAS_NOTARY_KEY_ID: ${{ secrets.ATLAS_NOTARY_KEY_ID }}
|
||||
ATLAS_NOTARY_ISSUER_ID: ${{ secrets.ATLAS_NOTARY_ISSUER_ID }}
|
||||
ATLAS_NOTARY_API_KEY_BASE64: ${{ secrets.ATLAS_NOTARY_API_KEY_BASE64 }}
|
||||
run: |
|
||||
required_vars=(
|
||||
ATLAS_RELEASE_APP_CERT_P12_BASE64
|
||||
ATLAS_RELEASE_APP_CERT_P12_PASSWORD
|
||||
ATLAS_RELEASE_INSTALLER_CERT_P12_BASE64
|
||||
ATLAS_RELEASE_INSTALLER_CERT_P12_PASSWORD
|
||||
ATLAS_NOTARY_KEY_ID
|
||||
ATLAS_NOTARY_API_KEY_BASE64
|
||||
)
|
||||
|
||||
missing_vars=()
|
||||
for name in "${required_vars[@]}"; do
|
||||
if [[ -z "${!name:-}" ]]; then
|
||||
missing_vars+=("$name")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_vars[@]} -eq 0 ]]; then
|
||||
echo "packaging_mode=developer-id" >> "$GITHUB_OUTPUT"
|
||||
echo "prerelease=false" >> "$GITHUB_OUTPUT"
|
||||
echo "ATLAS_RELEASE_SIGNING_MODE=developer-id" >> "$GITHUB_ENV"
|
||||
echo "Using Developer ID release packaging"
|
||||
else
|
||||
echo "packaging_mode=development" >> "$GITHUB_OUTPUT"
|
||||
echo "prerelease=true" >> "$GITHUB_OUTPUT"
|
||||
echo "ATLAS_RELEASE_SIGNING_MODE=development" >> "$GITHUB_ENV"
|
||||
printf 'Falling back to development packaging; missing secrets: %s\n' "${missing_vars[*]}"
|
||||
fi
|
||||
|
||||
- name: Configure release signing
|
||||
if: steps.mode.outputs.packaging_mode == 'developer-id'
|
||||
env:
|
||||
ATLAS_RELEASE_APP_CERT_P12_BASE64: ${{ secrets.ATLAS_RELEASE_APP_CERT_P12_BASE64 }}
|
||||
ATLAS_RELEASE_APP_CERT_P12_PASSWORD: ${{ secrets.ATLAS_RELEASE_APP_CERT_P12_PASSWORD }}
|
||||
ATLAS_RELEASE_INSTALLER_CERT_P12_BASE64: ${{ secrets.ATLAS_RELEASE_INSTALLER_CERT_P12_BASE64 }}
|
||||
ATLAS_RELEASE_INSTALLER_CERT_P12_PASSWORD: ${{ secrets.ATLAS_RELEASE_INSTALLER_CERT_P12_PASSWORD }}
|
||||
ATLAS_NOTARY_KEY_ID: ${{ secrets.ATLAS_NOTARY_KEY_ID }}
|
||||
ATLAS_NOTARY_ISSUER_ID: ${{ secrets.ATLAS_NOTARY_ISSUER_ID }}
|
||||
ATLAS_NOTARY_API_KEY_BASE64: ${{ secrets.ATLAS_NOTARY_API_KEY_BASE64 }}
|
||||
run: ./scripts/atlas/setup-release-signing-ci.sh
|
||||
|
||||
- name: Provision local development signing identity
|
||||
if: steps.mode.outputs.packaging_mode == 'development'
|
||||
run: ./scripts/atlas/ensure-local-signing-identity.sh
|
||||
|
||||
- name: Validate signing prerequisites
|
||||
if: steps.mode.outputs.packaging_mode == 'developer-id'
|
||||
run: ./scripts/atlas/signing-preflight.sh
|
||||
|
||||
- name: Build and package Atlas native app
|
||||
run: ./scripts/atlas/package-native.sh
|
||||
|
||||
- name: Verify DMG can install to the user Applications folder
|
||||
run: KEEP_INSTALLED_APP=1 ./scripts/atlas/verify-dmg-install.sh
|
||||
|
||||
- name: Upload native release artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: atlas-native-release
|
||||
path: |
|
||||
dist/native/Atlas-for-Mac.zip
|
||||
dist/native/Atlas-for-Mac.dmg
|
||||
dist/native/Atlas-for-Mac.pkg
|
||||
dist/native/Atlas-for-Mac.sha256
|
||||
retention-days: 1
|
||||
|
||||
release:
|
||||
name: Publish Release
|
||||
needs: build
|
||||
needs:
|
||||
- build
|
||||
- native
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -70,6 +163,24 @@ jobs:
|
||||
pattern: binaries-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download native release artifacts
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
with:
|
||||
name: atlas-native-release
|
||||
path: bin
|
||||
|
||||
- name: Generate release body
|
||||
run: |
|
||||
if [[ "${{ needs.native.outputs.packaging_mode }}" == "development" ]]; then
|
||||
{
|
||||
echo "Native macOS assets in this tag were packaged in development mode because Developer ID release-signing credentials were not configured for this run."
|
||||
echo
|
||||
echo "These \`.zip\`, \`.dmg\`, and \`.pkg\` files are intended for internal testing or developer use. macOS Gatekeeper may require \`Open Anyway\` or a right-click \`Open\` flow before launch."
|
||||
} > RELEASE_BODY.md
|
||||
else
|
||||
echo "Native macOS assets in this tag were packaged in CI using Developer ID signing and notarization, then uploaded alongside the existing command-line release artifacts." > RELEASE_BODY.md
|
||||
fi
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R bin/
|
||||
|
||||
@@ -91,6 +202,10 @@ jobs:
|
||||
bin/analyze-darwin-*
|
||||
bin/status-darwin-*
|
||||
bin/binaries-darwin-*.tar.gz
|
||||
bin/Atlas-for-Mac.zip
|
||||
bin/Atlas-for-Mac.dmg
|
||||
bin/Atlas-for-Mac.pkg
|
||||
bin/Atlas-for-Mac.sha256
|
||||
bin/SHA256SUMS
|
||||
|
||||
- name: Create Release
|
||||
@@ -99,6 +214,7 @@ jobs:
|
||||
with:
|
||||
name: ${{ github.ref_name }}
|
||||
files: bin/*
|
||||
body_path: RELEASE_BODY.md
|
||||
generate_release_notes: false
|
||||
draft: false
|
||||
prerelease: false
|
||||
prerelease: ${{ needs.native.outputs.prerelease == 'true' }}
|
||||
|
||||
@@ -100,11 +100,11 @@ final class AtlasAppModel: ObservableObject {
|
||||
}
|
||||
|
||||
var appVersion: String {
|
||||
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.1"
|
||||
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.2"
|
||||
}
|
||||
|
||||
var appBuild: String {
|
||||
Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "2"
|
||||
Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "3"
|
||||
}
|
||||
|
||||
func checkForUpdate() async {
|
||||
|
||||
@@ -458,7 +458,7 @@
|
||||
buildSettings = {
|
||||
AD_HOC_CODE_SIGNING_ALLOWED = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleShortVersionString = "$(MARKETING_VERSION)";
|
||||
INFOPLIST_KEY_CFBundleVersion = "$(CURRENT_PROJECT_VERSION)";
|
||||
@@ -467,7 +467,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
MARKETING_VERSION = 1.0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.atlasformac.app.worker;
|
||||
PRODUCT_NAME = AtlasWorkerXPC;
|
||||
SDKROOT = macosx;
|
||||
@@ -535,7 +535,7 @@
|
||||
buildSettings = {
|
||||
AD_HOC_CODE_SIGNING_ALLOWED = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleShortVersionString = "$(MARKETING_VERSION)";
|
||||
INFOPLIST_KEY_CFBundleVersion = "$(CURRENT_PROJECT_VERSION)";
|
||||
@@ -544,7 +544,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
MARKETING_VERSION = 1.0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.atlasformac.app.worker;
|
||||
PRODUCT_NAME = AtlasWorkerXPC;
|
||||
SDKROOT = macosx;
|
||||
@@ -557,7 +557,7 @@
|
||||
AD_HOC_CODE_SIGNING_ALLOWED = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Atlas for Mac";
|
||||
INFOPLIST_KEY_CFBundleShortVersionString = "$(MARKETING_VERSION)";
|
||||
@@ -569,7 +569,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
MARKETING_VERSION = 1.0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.atlasformac.app;
|
||||
PRODUCT_MODULE_NAME = AtlasApp;
|
||||
PRODUCT_NAME = "Atlas for Mac";
|
||||
@@ -665,7 +665,7 @@
|
||||
AD_HOC_CODE_SIGNING_ALLOWED = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Atlas for Mac";
|
||||
INFOPLIST_KEY_CFBundleShortVersionString = "$(MARKETING_VERSION)";
|
||||
@@ -677,7 +677,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.0.1;
|
||||
MARKETING_VERSION = 1.0.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.atlasformac.app;
|
||||
PRODUCT_MODULE_NAME = AtlasApp;
|
||||
PRODUCT_NAME = "Atlas for Mac";
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.2] - 2026-03-14
|
||||
|
||||
### Added
|
||||
|
||||
- Tag-driven GitHub Releases now publish Atlas native `.zip`, `.dmg`, and `.pkg` assets in addition to the legacy command-line binaries.
|
||||
- Added `scripts/atlas/prepare-release.sh` to align app version, build number, and changelog scaffolding before tagging a release.
|
||||
|
||||
### Changed
|
||||
|
||||
- Release automation now falls back to a development-signed prerelease path when `Developer ID` signing credentials are unavailable, instead of blocking native packaging entirely.
|
||||
- README installation guidance now distinguishes signed public releases from development prereleases and recommends local stable signing for developer packaging.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `package-native.sh` and `signing-preflight.sh` now support `notarytool` profiles stored in a non-default keychain, which unblocks CI-based notarization.
|
||||
|
||||
## [1.0.1] - 2026-03-13
|
||||
|
||||
### Added
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
- Distribution target: `Developer ID + Hardened Runtime + Notarization`
|
||||
- Initial release target: direct distribution, not Mac App Store
|
||||
- Native packaging currently uses `xcodegen + xcodebuild`, embeds the helper into `Contents/Helpers/`, and emits `.zip`, `.dmg`, and `.pkg` distribution artifacts.
|
||||
- Tagged GitHub Releases reuse the same native packaging scripts in CI and publish `.zip`, `.dmg`, `.pkg`, and checksum assets.
|
||||
- When release signing credentials are configured, CI signs and notarizes those assets; otherwise it falls back to a local development signing identity and marks the GitHub Release as a prerelease.
|
||||
- Local internal packaging now prefers a stable non-ad-hoc app signature when a usable identity is available, so macOS TCC decisions can survive rebuilds more reliably during development.
|
||||
- If Apple release certificates are unavailable, Atlas can fall back to a repo-managed local signing keychain for stable app-bundle identity; public release artifacts still require `Developer ID`.
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
|
||||
- Destructive helper actions use a structured executable boundary with path validation
|
||||
- Native MVP packaging uses `xcodegen + xcodebuild`, then embeds the helper into the app bundle
|
||||
- Tagged GitHub Releases should publish the native `.zip`, `.dmg`, and `.pkg` assets from CI using the same packaging scripts as local release builds
|
||||
- If CI lacks `Developer ID` release credentials, tagged native assets may still be published as development-signed prereleases instead of blocking the packaging path entirely
|
||||
- Signing and notarization remain optional release-time steps driven by credentials
|
||||
- Internal packaging should prefer a stable local app-signing identity over ad hoc signing whenever possible so macOS permission state does not drift across rebuilds
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ Turn Atlas for Mac from an installable local build into a publicly distributable
|
||||
- `ATLAS_CODESIGN_KEYCHAIN`
|
||||
- `ATLAS_INSTALLER_SIGN_IDENTITY`
|
||||
- `ATLAS_NOTARY_PROFILE`
|
||||
- `ATLAS_NOTARY_KEYCHAIN` (optional; required when the notary profile lives in a non-default keychain such as CI)
|
||||
|
||||
## Stable Local Signing
|
||||
|
||||
@@ -43,6 +44,28 @@ Run:
|
||||
|
||||
If preflight passes, the current machine is ready for signed packaging.
|
||||
|
||||
## Version Prep
|
||||
|
||||
Before pushing a release tag, align the app version, build number, and changelog skeleton:
|
||||
|
||||
```bash
|
||||
./scripts/atlas/prepare-release.sh 1.0.2
|
||||
```
|
||||
|
||||
Optional arguments:
|
||||
|
||||
```bash
|
||||
./scripts/atlas/prepare-release.sh 1.0.2 3 2026-03-14
|
||||
```
|
||||
|
||||
This updates:
|
||||
|
||||
- `project.yml`
|
||||
- `Apps/AtlasApp/Sources/AtlasApp/AtlasAppModel.swift`
|
||||
- `CHANGELOG.md`
|
||||
|
||||
The script increments `CURRENT_PROJECT_VERSION` automatically when you omit the build number. Review the new changelog section before creating the `V1.0.2` tag.
|
||||
|
||||
## Signed Packaging
|
||||
|
||||
Run:
|
||||
@@ -56,6 +79,12 @@ ATLAS_NOTARY_PROFILE="<profile-name>" \
|
||||
|
||||
This signs the app bundle, emits `.zip`, `.dmg`, and `.pkg`, submits artifacts for notarization, and staples results when credentials are available.
|
||||
|
||||
If the notary profile is stored in a non-default keychain, also set:
|
||||
|
||||
```bash
|
||||
ATLAS_NOTARY_KEYCHAIN="/path/to/release.keychain-db"
|
||||
```
|
||||
|
||||
## Install Verification
|
||||
|
||||
After packaging, validate the DMG installation path with:
|
||||
@@ -64,7 +93,52 @@ After packaging, validate the DMG installation path with:
|
||||
KEEP_INSTALLED_APP=1 ./scripts/atlas/verify-dmg-install.sh
|
||||
```
|
||||
|
||||
## GitHub Tag Release Automation
|
||||
|
||||
Tagged pushes matching `V*` now reuse the same packaging flow in CI and attach native release assets to the GitHub Release created by `.github/workflows/release.yml`.
|
||||
|
||||
Required GitHub Actions secrets:
|
||||
|
||||
- `ATLAS_RELEASE_APP_CERT_P12_BASE64`
|
||||
- `ATLAS_RELEASE_APP_CERT_P12_PASSWORD`
|
||||
- `ATLAS_RELEASE_INSTALLER_CERT_P12_BASE64`
|
||||
- `ATLAS_RELEASE_INSTALLER_CERT_P12_PASSWORD`
|
||||
- `ATLAS_NOTARY_KEY_ID`
|
||||
- `ATLAS_NOTARY_ISSUER_ID` for Team API keys; omit only if you intentionally use an Individual API key
|
||||
- `ATLAS_NOTARY_API_KEY_BASE64`
|
||||
|
||||
If those secrets are present, the workflow bootstraps a temporary keychain with `./scripts/atlas/setup-release-signing-ci.sh`, stores a `notarytool` profile there, derives `ATLAS_VERSION` from the pushed tag name, then runs `./scripts/atlas/package-native.sh`.
|
||||
|
||||
If those secrets are missing, the workflow automatically falls back to:
|
||||
|
||||
- `./scripts/atlas/ensure-local-signing-identity.sh`
|
||||
- local development signing for the app bundle
|
||||
- unsigned installer packaging if no installer identity exists
|
||||
- no notarization
|
||||
- GitHub Release marked as `prerelease`
|
||||
|
||||
Release flow:
|
||||
|
||||
```bash
|
||||
git tag -a V1.0.2 -m "Release V1.0.2"
|
||||
git push origin V1.0.2
|
||||
```
|
||||
|
||||
That tag creates one GitHub Release containing:
|
||||
|
||||
- legacy Go binaries and Homebrew tarballs from the existing release pipeline
|
||||
- `Atlas-for-Mac.zip`
|
||||
- `Atlas-for-Mac.dmg`
|
||||
- `Atlas-for-Mac.pkg`
|
||||
- native and aggregate SHA-256 checksum files
|
||||
|
||||
Packaging mode by credential state:
|
||||
|
||||
- `Developer ID secrets present` -> signed and notarized native assets, normal GitHub Release
|
||||
- `Developer ID secrets missing` -> development-signed native assets, GitHub `prerelease`
|
||||
|
||||
## Current Repo State
|
||||
|
||||
- Internal packaging can now use a stable local app-signing identity instead of ad hoc signing.
|
||||
- Signed/notarized release artifacts remain blocked only by missing Apple release credentials on this machine.
|
||||
- Tagged GitHub Releases can still publish development-mode native assets without those credentials.
|
||||
|
||||
@@ -30,6 +30,8 @@ Download the latest release from the [Releases](https://github.com/CSZHK/CleanMy
|
||||
- **`.zip`** — Extract and move Atlas.app to your Applications folder.
|
||||
- **`.pkg`** — Run the installer package for guided installation.
|
||||
|
||||
Prefer the latest non-prerelease release if you want the normal public install path. GitHub prereleases may contain development-signed builds intended for testing; those builds can require `Open Anyway` or a right-click `Open` flow before launch.
|
||||
|
||||
### Requirements
|
||||
|
||||
- macOS 14.0 (Sonoma) or later
|
||||
@@ -51,7 +53,7 @@ xcodegen generate
|
||||
open Atlas.xcodeproj
|
||||
```
|
||||
|
||||
> **Note**: The app is currently unsigned. On first launch, you may need to right-click and select "Open" to bypass Gatekeeper, or go to System Settings > Privacy & Security to allow it.
|
||||
> **Note**: Atlas release assets can be either `Developer ID signed + notarized` or `development prerelease` builds, depending on the release. If you install a prerelease or a local build, macOS may require `Open Anyway` or a right-click `Open` flow before launch.
|
||||
|
||||
## MVP Modules
|
||||
|
||||
@@ -115,9 +117,12 @@ open Atlas.xcodeproj
|
||||
### Package `.zip`, `.dmg`, and `.pkg` artifacts
|
||||
|
||||
```bash
|
||||
./scripts/atlas/ensure-local-signing-identity.sh
|
||||
./scripts/atlas/package-native.sh
|
||||
```
|
||||
|
||||
The local signing step is recommended on machines that do not have Apple release certificates. It gives local and prerelease builds a stable development signature instead of falling back to ad hoc packaging.
|
||||
|
||||
### Run focused tests
|
||||
|
||||
```bash
|
||||
|
||||
@@ -30,6 +30,8 @@ Atlas for Mac 是一个独立的开源项目,与 Apple、Mole 上游作者或
|
||||
- **`.zip`** - 解压后将 Atlas.app 移动到 Applications 文件夹。
|
||||
- **`.pkg`** - 运行安装包,按向导完成安装。
|
||||
|
||||
如果你想要正常的公开安装路径,优先下载最新的非 `prerelease` 版本。GitHub 上的 `prerelease` 版本可能包含用于测试的开发签名构建,这类构建在首次启动时可能需要 `仍要打开` 或右键 `打开`。
|
||||
|
||||
### 系统要求
|
||||
|
||||
- macOS 14.0(Sonoma)或更高版本
|
||||
@@ -51,7 +53,7 @@ xcodegen generate
|
||||
open Atlas.xcodeproj
|
||||
```
|
||||
|
||||
> **说明**:应用当前尚未签名。首次启动时,你可能需要右键点击应用并选择“打开”以绕过 Gatekeeper,或前往“系统设置 > 隐私与安全性”手动允许启动。
|
||||
> **说明**:Atlas 的发布包可能是 `Developer ID 签名 + 公证` 的正式构建,也可能是开发预发布构建,具体取决于该次发布。如果你安装的是预发布版本或本地构建版本,macOS 在首次启动时可能要求你使用 `仍要打开` 或右键 `打开`。
|
||||
|
||||
## MVP 模块
|
||||
|
||||
@@ -115,9 +117,12 @@ open Atlas.xcodeproj
|
||||
### 打包 `.zip`、`.dmg` 和 `.pkg` 产物
|
||||
|
||||
```bash
|
||||
./scripts/atlas/ensure-local-signing-identity.sh
|
||||
./scripts/atlas/package-native.sh
|
||||
```
|
||||
|
||||
对于没有 Apple 发布证书的机器,建议先执行本地签名初始化。这样本地包和预发布开发包会带有稳定的开发签名,而不是退回到 ad hoc 打包。
|
||||
|
||||
### 运行聚焦测试
|
||||
|
||||
```bash
|
||||
|
||||
@@ -38,8 +38,8 @@ targets:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.atlasformac.app.worker
|
||||
PRODUCT_NAME: AtlasWorkerXPC
|
||||
MARKETING_VERSION: "1.0.1"
|
||||
CURRENT_PROJECT_VERSION: 2
|
||||
MARKETING_VERSION: "1.0.2"
|
||||
CURRENT_PROJECT_VERSION: 3
|
||||
GENERATE_INFOPLIST_FILE: YES
|
||||
INFOPLIST_KEY_CFBundleShortVersionString: $(MARKETING_VERSION)
|
||||
INFOPLIST_KEY_CFBundleVersion: $(CURRENT_PROJECT_VERSION)
|
||||
@@ -64,8 +64,8 @@ targets:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.atlasformac.app
|
||||
PRODUCT_NAME: Atlas for Mac
|
||||
PRODUCT_MODULE_NAME: AtlasApp
|
||||
MARKETING_VERSION: "1.0.1"
|
||||
CURRENT_PROJECT_VERSION: 2
|
||||
MARKETING_VERSION: "1.0.2"
|
||||
CURRENT_PROJECT_VERSION: 3
|
||||
GENERATE_INFOPLIST_FILE: YES
|
||||
INFOPLIST_KEY_CFBundleShortVersionString: $(MARKETING_VERSION)
|
||||
INFOPLIST_KEY_CFBundleVersion: $(CURRENT_PROJECT_VERSION)
|
||||
|
||||
@@ -21,6 +21,7 @@ APP_SIGNING_KEYCHAIN="$(atlas_resolve_app_signing_keychain "$APP_SIGN_IDENTITY")
|
||||
APP_SIGNING_MODE="$(atlas_signing_mode_for_identity "$APP_SIGN_IDENTITY")"
|
||||
INSTALLER_SIGN_IDENTITY="$(atlas_resolve_installer_signing_identity)"
|
||||
NOTARY_PROFILE="${ATLAS_NOTARY_PROFILE:-}"
|
||||
NOTARY_KEYCHAIN="${ATLAS_NOTARY_KEYCHAIN:-}"
|
||||
|
||||
mkdir -p "$DIST_DIR"
|
||||
|
||||
@@ -122,10 +123,15 @@ echo "Installer package: $PKG_PATH"
|
||||
echo "Checksums: $SHA_PATH"
|
||||
|
||||
if [[ -n "$NOTARY_PROFILE" && "$APP_SIGNING_MODE" == "developer-id" && -n "$INSTALLER_SIGN_IDENTITY" ]]; then
|
||||
xcrun notarytool submit "$PKG_PATH" --keychain-profile "$NOTARY_PROFILE" --wait
|
||||
notarytool_args=(--keychain-profile "$NOTARY_PROFILE")
|
||||
if [[ -n "$NOTARY_KEYCHAIN" ]]; then
|
||||
notarytool_args+=(--keychain "$NOTARY_KEYCHAIN")
|
||||
fi
|
||||
|
||||
xcrun notarytool submit "$PKG_PATH" "${notarytool_args[@]}" --wait
|
||||
xcrun stapler staple "$PKG_PATH"
|
||||
xcrun notarytool submit "$DMG_PATH" --keychain-profile "$NOTARY_PROFILE" --wait
|
||||
xcrun notarytool submit "$ZIP_PATH" --keychain-profile "$NOTARY_PROFILE" --wait
|
||||
xcrun notarytool submit "$DMG_PATH" "${notarytool_args[@]}" --wait
|
||||
xcrun notarytool submit "$ZIP_PATH" "${notarytool_args[@]}" --wait
|
||||
xcrun stapler staple "$PACKAGED_APP_PATH"
|
||||
/usr/bin/ditto -c -k --sequesterRsrc --keepParent "$PACKAGED_APP_PATH" "$ZIP_PATH"
|
||||
(
|
||||
|
||||
105
scripts/atlas/prepare-release.sh
Executable file
105
scripts/atlas/prepare-release.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
PROJECT_FILE="$ROOT_DIR/project.yml"
|
||||
APP_MODEL_FILE="$ROOT_DIR/Apps/AtlasApp/Sources/AtlasApp/AtlasAppModel.swift"
|
||||
CHANGELOG_FILE="$ROOT_DIR/CHANGELOG.md"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/atlas/prepare-release.sh <version> [build-number] [release-date]
|
||||
|
||||
Examples:
|
||||
./scripts/atlas/prepare-release.sh 1.0.2
|
||||
./scripts/atlas/prepare-release.sh 1.0.2 3
|
||||
./scripts/atlas/prepare-release.sh 1.0.2 3 2026-03-14
|
||||
|
||||
Behavior:
|
||||
- updates MARKETING_VERSION and CURRENT_PROJECT_VERSION in project.yml
|
||||
- updates AtlasApp fallback version/build strings
|
||||
- inserts a changelog section for the requested version if it does not already exist
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ $# -lt 1 || $# -gt 3 ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="$1"
|
||||
BUILD_NUMBER="${2:-}"
|
||||
RELEASE_DATE="${3:-$(date +%F)}"
|
||||
|
||||
if [[ ! "$VERSION" =~ ^[0-9]+(\.[0-9]+){1,2}([.-][0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "Invalid version: $VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$BUILD_NUMBER" && ! "$BUILD_NUMBER" =~ ^[0-9]+$ ]]; then
|
||||
echo "Build number must be numeric: $BUILD_NUMBER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$RELEASE_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
|
||||
echo "Release date must use YYYY-MM-DD: $RELEASE_DATE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$PROJECT_FILE" || ! -f "$APP_MODEL_FILE" || ! -f "$CHANGELOG_FILE" ]]; then
|
||||
echo "Expected release files are missing." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$BUILD_NUMBER" ]]; then
|
||||
current_build="$(
|
||||
sed -n 's/.*CURRENT_PROJECT_VERSION: \([0-9][0-9]*\).*/\1/p' "$PROJECT_FILE" | head -1
|
||||
)"
|
||||
if [[ -z "$current_build" ]]; then
|
||||
echo "Could not determine current build number from project.yml" >&2
|
||||
exit 1
|
||||
fi
|
||||
BUILD_NUMBER="$((current_build + 1))"
|
||||
fi
|
||||
|
||||
current_version="$(
|
||||
sed -n 's/.*MARKETING_VERSION: "\(.*\)"/\1/p' "$PROJECT_FILE" | head -1
|
||||
)"
|
||||
|
||||
perl -0pi -e 's/MARKETING_VERSION: "[^"]+"/MARKETING_VERSION: "'"$VERSION"'"/g' "$PROJECT_FILE"
|
||||
perl -0pi -e 's/CURRENT_PROJECT_VERSION: \d+/CURRENT_PROJECT_VERSION: '"$BUILD_NUMBER"'/g' "$PROJECT_FILE"
|
||||
perl -0pi -e 's/(CFBundleShortVersionString"\] as\? String \?\? )"[^"]+"/${1}"'"$VERSION"'"/' "$APP_MODEL_FILE"
|
||||
perl -0pi -e 's/(CFBundleVersion"\] as\? String \?\? )"[^"]+"/${1}"'"$BUILD_NUMBER"'"/' "$APP_MODEL_FILE"
|
||||
|
||||
if ! grep -Fq "## [$VERSION] - $RELEASE_DATE" "$CHANGELOG_FILE"; then
|
||||
tmpfile="$(mktemp "${TMPDIR:-/tmp}/atlas-changelog.XXXXXX")"
|
||||
awk -v version="$VERSION" -v date="$RELEASE_DATE" '
|
||||
BEGIN { inserted = 0 }
|
||||
{
|
||||
print
|
||||
if (!inserted && $0 == "## [Unreleased]") {
|
||||
print ""
|
||||
print "## [" version "] - " date
|
||||
print ""
|
||||
print "### Added"
|
||||
print ""
|
||||
print "### Changed"
|
||||
print ""
|
||||
print "### Fixed"
|
||||
print ""
|
||||
inserted = 1
|
||||
}
|
||||
}
|
||||
' "$CHANGELOG_FILE" > "$tmpfile"
|
||||
mv "$tmpfile" "$CHANGELOG_FILE"
|
||||
fi
|
||||
|
||||
printf 'Prepared Atlas release files\n'
|
||||
printf 'Previous version: %s\n' "${current_version:-UNKNOWN}"
|
||||
printf 'New version: %s\n' "$VERSION"
|
||||
printf 'Build number: %s\n' "$BUILD_NUMBER"
|
||||
printf 'Release date: %s\n' "$RELEASE_DATE"
|
||||
printf 'Updated: %s\n' "$PROJECT_FILE"
|
||||
printf 'Updated: %s\n' "$APP_MODEL_FILE"
|
||||
printf 'Updated: %s\n' "$CHANGELOG_FILE"
|
||||
151
scripts/atlas/setup-release-signing-ci.sh
Executable file
151
scripts/atlas/setup-release-signing-ci.sh
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
require_env() {
|
||||
local name="$1"
|
||||
if [[ -z "${!name:-}" ]]; then
|
||||
echo "Missing required environment variable: $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
write_env() {
|
||||
local name="$1"
|
||||
local value="$2"
|
||||
|
||||
if [[ -n "${GITHUB_ENV:-}" ]]; then
|
||||
printf '%s=%s\n' "$name" "$value" >> "$GITHUB_ENV"
|
||||
else
|
||||
printf 'export %s=%q\n' "$name" "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
append_keychain_search_list() {
|
||||
local keychain_path="$1"
|
||||
local current_keychains=()
|
||||
local line=""
|
||||
|
||||
while IFS= read -r line; do
|
||||
line="${line#"${line%%[![:space:]]*}"}"
|
||||
line="${line%\"}"
|
||||
line="${line#\"}"
|
||||
[[ -n "$line" ]] && current_keychains+=("$line")
|
||||
done < <(security list-keychains -d user 2> /dev/null || true)
|
||||
|
||||
if printf '%s\n' "${current_keychains[@]}" | grep -Fx "$keychain_path" > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
security list-keychains -d user -s "$keychain_path" "${current_keychains[@]}" > /dev/null
|
||||
}
|
||||
|
||||
decode_base64_to_file() {
|
||||
local encoded="$1"
|
||||
local destination="$2"
|
||||
|
||||
printf '%s' "$encoded" | base64 --decode > "$destination"
|
||||
}
|
||||
|
||||
detect_identity() {
|
||||
local policy="$1"
|
||||
local prefix="$2"
|
||||
local keychain_path="$3"
|
||||
|
||||
security find-identity -v -p "$policy" "$keychain_path" 2> /dev/null |
|
||||
sed -n "s/.*\"\\($prefix.*\\)\"/\\1/p" |
|
||||
head -1
|
||||
}
|
||||
|
||||
require_env ATLAS_RELEASE_APP_CERT_P12_BASE64
|
||||
require_env ATLAS_RELEASE_APP_CERT_P12_PASSWORD
|
||||
require_env ATLAS_RELEASE_INSTALLER_CERT_P12_BASE64
|
||||
require_env ATLAS_RELEASE_INSTALLER_CERT_P12_PASSWORD
|
||||
require_env ATLAS_NOTARY_KEY_ID
|
||||
require_env ATLAS_NOTARY_API_KEY_BASE64
|
||||
|
||||
KEYCHAIN_PATH="${ATLAS_RELEASE_KEYCHAIN_PATH:-${RUNNER_TEMP:-${TMPDIR:-/tmp}}/atlas-release.keychain-db}"
|
||||
KEYCHAIN_PASSWORD="${ATLAS_RELEASE_KEYCHAIN_PASSWORD:-$(uuidgen | tr '[:upper:]' '[:lower:]')}"
|
||||
NOTARY_PROFILE="${ATLAS_NOTARY_PROFILE:-atlas-release}"
|
||||
|
||||
tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/atlas-release-signing.XXXXXX")"
|
||||
cleanup() {
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
APP_CERT_PATH="$tmpdir/application-cert.p12"
|
||||
INSTALLER_CERT_PATH="$tmpdir/installer-cert.p12"
|
||||
NOTARY_KEY_PATH="$tmpdir/AuthKey.p8"
|
||||
|
||||
decode_base64_to_file "$ATLAS_RELEASE_APP_CERT_P12_BASE64" "$APP_CERT_PATH"
|
||||
decode_base64_to_file "$ATLAS_RELEASE_INSTALLER_CERT_P12_BASE64" "$INSTALLER_CERT_PATH"
|
||||
decode_base64_to_file "$ATLAS_NOTARY_API_KEY_BASE64" "$NOTARY_KEY_PATH"
|
||||
|
||||
if [[ -f "$KEYCHAIN_PATH" ]]; then
|
||||
rm -f "$KEYCHAIN_PATH"
|
||||
fi
|
||||
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" > /dev/null
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||||
append_keychain_search_list "$KEYCHAIN_PATH"
|
||||
|
||||
security import "$APP_CERT_PATH" \
|
||||
-k "$KEYCHAIN_PATH" \
|
||||
-P "$ATLAS_RELEASE_APP_CERT_P12_PASSWORD" \
|
||||
-f pkcs12 \
|
||||
-A \
|
||||
-T /usr/bin/codesign \
|
||||
-T /usr/bin/security \
|
||||
-T /usr/bin/productbuild > /dev/null
|
||||
|
||||
security import "$INSTALLER_CERT_PATH" \
|
||||
-k "$KEYCHAIN_PATH" \
|
||||
-P "$ATLAS_RELEASE_INSTALLER_CERT_P12_PASSWORD" \
|
||||
-f pkcs12 \
|
||||
-A \
|
||||
-T /usr/bin/codesign \
|
||||
-T /usr/bin/security \
|
||||
-T /usr/bin/productbuild > /dev/null
|
||||
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" > /dev/null
|
||||
|
||||
APP_IDENTITY="${ATLAS_CODESIGN_IDENTITY:-$(detect_identity codesigning 'Developer ID Application:' "$KEYCHAIN_PATH")}"
|
||||
INSTALLER_IDENTITY="${ATLAS_INSTALLER_SIGN_IDENTITY:-$(detect_identity basic 'Developer ID Installer:' "$KEYCHAIN_PATH")}"
|
||||
|
||||
if [[ -z "$APP_IDENTITY" ]]; then
|
||||
echo "Developer ID Application identity was not imported successfully." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$INSTALLER_IDENTITY" ]]; then
|
||||
echo "Developer ID Installer identity was not imported successfully." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
notarytool_args=(
|
||||
store-credentials
|
||||
"$NOTARY_PROFILE"
|
||||
--key "$NOTARY_KEY_PATH"
|
||||
--key-id "$ATLAS_NOTARY_KEY_ID"
|
||||
--keychain "$KEYCHAIN_PATH"
|
||||
--validate
|
||||
)
|
||||
|
||||
if [[ -n "${ATLAS_NOTARY_ISSUER_ID:-}" ]]; then
|
||||
notarytool_args+=(--issuer "$ATLAS_NOTARY_ISSUER_ID")
|
||||
fi
|
||||
|
||||
xcrun notarytool "${notarytool_args[@]}" > /dev/null
|
||||
|
||||
write_env ATLAS_CODESIGN_KEYCHAIN "$KEYCHAIN_PATH"
|
||||
write_env ATLAS_CODESIGN_IDENTITY "$APP_IDENTITY"
|
||||
write_env ATLAS_INSTALLER_SIGN_IDENTITY "$INSTALLER_IDENTITY"
|
||||
write_env ATLAS_NOTARY_PROFILE "$NOTARY_PROFILE"
|
||||
write_env ATLAS_NOTARY_KEYCHAIN "$KEYCHAIN_PATH"
|
||||
|
||||
printf 'Configured Atlas release signing\n'
|
||||
printf 'App identity: %s\n' "$APP_IDENTITY"
|
||||
printf 'Installer identity: %s\n' "$INSTALLER_IDENTITY"
|
||||
printf 'Notary profile: %s\n' "$NOTARY_PROFILE"
|
||||
printf 'Keychain: %s\n' "$KEYCHAIN_PATH"
|
||||
@@ -7,6 +7,7 @@ source "$ROOT_DIR/scripts/atlas/signing-common.sh"
|
||||
APP_IDENTITY_OVERRIDE="${ATLAS_CODESIGN_IDENTITY:-}"
|
||||
INSTALLER_IDENTITY_OVERRIDE="${ATLAS_INSTALLER_SIGN_IDENTITY:-}"
|
||||
NOTARY_PROFILE_OVERRIDE="${ATLAS_NOTARY_PROFILE:-}"
|
||||
NOTARY_KEYCHAIN_OVERRIDE="${ATLAS_NOTARY_KEYCHAIN:-}"
|
||||
|
||||
codesign_output="$(security find-identity -v -p codesigning 2> /dev/null || true)"
|
||||
basic_output="$(security find-identity -v -p basic 2> /dev/null || true)"
|
||||
@@ -26,6 +27,9 @@ printf '======================\n'
|
||||
printf 'Developer ID Application: %s\n' "${app_identity:-MISSING}"
|
||||
printf 'Developer ID Installer: %s\n' "${installer_identity:-MISSING}"
|
||||
printf 'Notary profile: %s\n' "${NOTARY_PROFILE_OVERRIDE:-MISSING}"
|
||||
if [[ -n "$NOTARY_KEYCHAIN_OVERRIDE" ]]; then
|
||||
printf 'Notary keychain: %s\n' "$NOTARY_KEYCHAIN_OVERRIDE"
|
||||
fi
|
||||
if [[ -n "$local_identity" ]]; then
|
||||
printf 'Stable local app identity: %s\n' "$local_identity"
|
||||
else
|
||||
@@ -47,7 +51,12 @@ if [[ -z "$NOTARY_PROFILE_OVERRIDE" ]]; then
|
||||
fi
|
||||
|
||||
if [[ -n "$NOTARY_PROFILE_OVERRIDE" ]]; then
|
||||
if xcrun notarytool history --keychain-profile "$NOTARY_PROFILE_OVERRIDE" > /dev/null 2>&1; then
|
||||
notarytool_args=(history --keychain-profile "$NOTARY_PROFILE_OVERRIDE")
|
||||
if [[ -n "$NOTARY_KEYCHAIN_OVERRIDE" ]]; then
|
||||
notarytool_args+=(--keychain "$NOTARY_KEYCHAIN_OVERRIDE")
|
||||
fi
|
||||
|
||||
if xcrun notarytool "${notarytool_args[@]}" > /dev/null 2>&1; then
|
||||
echo '✓ notarytool profile is usable'
|
||||
else
|
||||
echo '✗ notarytool profile could not be validated'
|
||||
|
||||
Reference in New Issue
Block a user