chore(release): prepare V1.0.2

This commit is contained in:
zhukang
2026-03-14 22:41:05 +08:00
parent 86e6ea1d80
commit 40405f1993
14 changed files with 513 additions and 22 deletions

View File

@@ -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' }}

View File

@@ -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 {

View File

@@ -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";

View File

@@ -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

View File

@@ -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`.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -30,6 +30,8 @@ Atlas for Mac 是一个独立的开源项目,与 Apple、Mole 上游作者或
- **`.zip`** - 解压后将 Atlas.app 移动到 Applications 文件夹。
- **`.pkg`** - 运行安装包,按向导完成安装。
如果你想要正常的公开安装路径,优先下载最新的非 `prerelease` 版本。GitHub 上的 `prerelease` 版本可能包含用于测试的开发签名构建,这类构建在首次启动时可能需要 `仍要打开` 或右键 `打开`
### 系统要求
- macOS 14.0Sonoma或更高版本
@@ -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

View File

@@ -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)

View File

@@ -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
View 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"

View 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"

View File

@@ -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'