Skip to content
Open
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
157 changes: 157 additions & 0 deletions plugins/psd-productivity/skills/psd-sign/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
name: psd-sign
description: Sign, notarize, and package a macOS .app into a .pkg for PSD Jamf Self Service deployment. Handles the full Apple Developer ID pipeline.
triggers:
- "sign app"
- "sign and package"
- "notarize app"
- "package for jamf"
- "build pkg"
- "psd-sign"
allowed-tools: Bash, Read
version: 0.1.0
---

# PSD macOS App Signing & Packaging

Sign, notarize, and package a macOS `.app` into a `.pkg` for deployment via Jamf Self Service.

## PSD Constants

These are embedded in the workflow and should not need to change unless PSD's Apple Developer account changes.

- **Team ID:** `87DL7L9GU6`
- **App Signing Identity:** `Developer ID Application: Peninsula School District (87DL7L9GU6)`
- **Installer Signing Identity:** `Developer ID Installer: Peninsula School District (87DL7L9GU6)`

## Prerequisites

- macOS with Xcode Command Line Tools installed
- PSD Developer ID certificates installed on the machine (both Application and Installer)
- An Apple ID that is a member of the PSD Apple Developer account
- An app-specific password for that Apple ID (generate at https://appleid.apple.com)
- A built `.app` bundle ready for signing

## Workflow

When invoked, collect the following from the user before proceeding:

1. **App path** — full path to the `.app` bundle (may be provided as an argument)
2. **Apple ID** — the `@psd401.net` account with Developer ID access
3. **App-specific password** — SECURITY: never log, echo, or store this value
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Collecting the app-specific password at runtime and passing it on the command line is a security risk, as it can be exposed in process lists and shell history. It's more secure to have the user store the credential in the keychain once using xcrun notarytool store-credentials and then provide a profile name to the script. This also improves usability by not requiring the password for every run.

Suggested change
3. **App-specific password**SECURITY: never log, echo, or store this value
3. **Notary keychain profile**name of the keychain profile for `notarytool` (create with `xcrun notarytool store-credentials`)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied. The workflow inputs now ask for a keychain profile name instead of an Apple ID + app-specific password. The one-time setup is documented in Prerequisites:

2. **Notary keychain profile** — name of the keychain profile for `notarytool` (create once with `xcrun notarytool store-credentials`)

Prerequisites now reads:

- A notarytool keychain profile stored on the machine — create once with:
  `xcrun notarytool store-credentials "<profile-name>" --apple-id <your@psd401.net> --team-id 87DL7L9GU6 --password <app-specific-password>`

Fix committed to psd401/psd-claude-plugins:add-psd-sign-skill — please pull or apply to your fork branch.


Generated by Claude Code

4. **Version number** — semver string for the package (e.g., `1.0.0`)
5. **Bundle identifier** — defaults to `net.psd401.<appname-lowercase>`

Confirm the configuration with the user, then run each step sequentially. Stop immediately on any failure and report the error.

### Step 1: Remove quarantine

Remove macOS quarantine extended attributes that would interfere with signing.

```bash
xattr -cr "$APP_PATH"
```

### Step 2: Sign with Developer ID

Deep-sign the app bundle with hardened runtime for notarization compatibility.

```bash
codesign --deep --force --options runtime \
--sign "Developer ID Application: Peninsula School District (87DL7L9GU6)" \
"$APP_PATH"
```

### Step 3: Zip for notarization

Create a zip archive for submission to Apple's notary service.

```bash
ditto -c -k --keepParent "$APP_PATH" "/tmp/${APP_NAME}.zip"
```

### Step 4: Notarize the .app

Submit to Apple's notary service and wait for approval. This typically takes 2-10 minutes.

```bash
xcrun notarytool submit "/tmp/${APP_NAME}.zip" \
--apple-id "$APPLE_ID" \
--team-id 87DL7L9GU6 \
--password "$APP_PASSWORD" \
--wait
Comment on lines +78 to +82
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To avoid exposing the app-specific password on the command line, which is a security risk, you should use a keychain profile. This change assumes the workflow is updated to ask for a keychain profile name (e.g., in a variable like $NOTARY_KEYCHAIN_PROFILE) instead of the password itself. Using a profile also simplifies the command as apple-id and team-id are not needed if stored in the profile.

Suggested change
xcrun notarytool submit "/tmp/${APP_NAME}.zip" \
--apple-id "$APPLE_ID" \
--team-id 87DL7L9GU6 \
--password "$APP_PASSWORD" \
--wait
# Use keychain profile for security. First, store credentials:
# xcrun notarytool store-credentials "YOUR_PROFILE_NAME" --apple-id ... --team-id ... --password ...
xcrun notarytool submit "/tmp/${APP_NAME}.zip" \
--keychain-profile "$NOTARY_KEYCHAIN_PROFILE" \
--wait

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied. Step 4 now uses --keychain-profile — no Apple ID, team ID, or password on the command line:

xcrun notarytool submit "/tmp/${APP_NAME}.zip" \
  --keychain-profile "$NOTARY_KEYCHAIN_PROFILE" \
  --wait

Fix committed to psd401/psd-claude-plugins:add-psd-sign-skill.


Generated by Claude Code

```

### Step 5: Staple the .app

Attach the notarization ticket to the app so it works offline.

```bash
xcrun stapler staple "$APP_PATH"
```

### Step 6: Build the .pkg

Package the signed, notarized app into an installer package for Jamf.

```bash
PAYLOAD_DIR=$(mktemp -d)
mkdir -p "$PAYLOAD_DIR/Applications"
cp -R "$APP_PATH" "$PAYLOAD_DIR/Applications/"
pkgbuild \
--root "$PAYLOAD_DIR" \
--identifier "$PKG_ID" \
--version "$VERSION" \
--install-location "/" \
--sign "Developer ID Installer: Peninsula School District (87DL7L9GU6)" \
"$HOME/Desktop/${APP_NAME}.pkg"
rm -rf "$PAYLOAD_DIR"
```

### Step 7: Notarize the .pkg

Submit the installer package to Apple's notary service and staple.

```bash
xcrun notarytool submit "$HOME/Desktop/${APP_NAME}.pkg" \
--apple-id "$APPLE_ID" \
--team-id 87DL7L9GU6 \
--password "$APP_PASSWORD" \
--wait
Comment on lines +116 to +120
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

As with the app notarization step, the package notarization should also use a keychain profile to avoid exposing the password on the command line. This is a security risk.

Suggested change
xcrun notarytool submit "$HOME/Desktop/${APP_NAME}.pkg" \
--apple-id "$APPLE_ID" \
--team-id 87DL7L9GU6 \
--password "$APP_PASSWORD" \
--wait
# Use keychain profile for security. First, store credentials:
# xcrun notarytool store-credentials "YOUR_PROFILE_NAME" --apple-id ... --team-id ... --password ...
xcrun notarytool submit "$HOME/Desktop/${APP_NAME}.pkg" \
--keychain-profile "$NOTARY_KEYCHAIN_PROFILE" \
--wait

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied. Step 7 now uses --keychain-profile for the .pkg notarization too:

xcrun notarytool submit "$HOME/Desktop/${APP_NAME}.pkg" \
  --keychain-profile "$NOTARY_KEYCHAIN_PROFILE" \
  --wait

Fix committed to psd401/psd-claude-plugins:add-psd-sign-skill.


Generated by Claude Code

xcrun stapler staple "$HOME/Desktop/${APP_NAME}.pkg"
```

### Step 8: Verify

Confirm both the app and package pass Gatekeeper validation.

```bash
spctl -a -vv "$APP_PATH"
pkgutil --check-signature "$HOME/Desktop/${APP_NAME}.pkg"
```

### Step 9: Final quarantine cleanup

Remove any residual quarantine attributes.

```bash
xattr -r -d com.apple.quarantine "$APP_PATH" 2>/dev/null || true
```

### Cleanup

Remove temporary files.

```bash
rm -f "/tmp/${APP_NAME}.zip"
```

## Output

The signed, notarized `.pkg` is placed on the user's Desktop at `~/Desktop/${APP_NAME}.pkg`. This file is ready to upload to Jamf Pro for Self Service distribution.

## Troubleshooting

- **"no identity found"** — Developer ID certificates are not installed. Open Keychain Access and verify both `Developer ID Application` and `Developer ID Installer` certs are present for Peninsula School District.
- **Notarization rejected** — Check the log with `xcrun notarytool log <submission-id> --apple-id ... --team-id 87DL7L9GU6 --password ...`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency and security, the troubleshooting command for checking notarization logs should also be updated to use the keychain profile instead of passing credentials on the command line.

Suggested change
- **Notarization rejected** — Check the log with `xcrun notarytool log <submission-id> --apple-id ... --team-id 87DL7L9GU6 --password ...`
- **Notarization rejected** — Check the log with `xcrun notarytool log <submission-id> --keychain-profile <profile-name>`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied. The troubleshooting command now uses --keychain-profile:

- **Notarization rejected** — Check the log with `xcrun notarytool log <submission-id> --keychain-profile <profile-name>`

Fix committed to psd401/psd-claude-plugins:add-psd-sign-skill.


Generated by Claude Code

- **"not valid on disk"** from `spctl` — The app was modified after signing. Re-run from Step 1.
Loading