a simple rust terminal ui (tui) for setting up alternative plc rotation keys driven by: secure enclave hardware (not synced) or software-based keys (synced to icloud)
plc secure-enclave touchid icloud atproto

Add release build steps

+107 -10
+7
.env.example
··· 1 + # Development signing (./build.sh or ./build.sh dev) 1 2 CODESIGN_IDENTITY="Apple Development: Your Name (XXXXXXXXXX)" 2 3 BUNDLE_ID="com.yourcompany.plc-touch" 3 4 TEAM_ID="XXXXXXXXXX" 5 + 6 + # Release signing (./build.sh release) 7 + # Requires a "Developer ID Application" certificate from developer.apple.com 8 + DEVELOPER_ID="Developer ID Application: Your Name (XXXXXXXXXX)" 9 + APPLE_ID="your@email.com" 10 + NOTARIZE_PASSWORD="xxxx-xxxx-xxxx-xxxx" # App-specific password from appleid.apple.com
+1
.gitignore
··· 10 10 11 11 # Apple signing (contains device UDIDs and developer certificates) 12 12 embedded.provisionprofile 13 + release.provisionprofile 13 14 entitlements.plist 14 15 15 16 # Claude Code local settings
+98 -9
build.sh
··· 14 14 # CODESIGN_IDENTITY="Apple Development: Your Name (XXXXXXXXXX)" 15 15 # BUNDLE_ID="com.yourcompany.plc-touch" 16 16 # TEAM_ID="XXXXXXXXXX" 17 - IDENTITY="${CODESIGN_IDENTITY:?Set CODESIGN_IDENTITY in .env or as env var}" 17 + # 18 + # For release builds, also set: 19 + # DEVELOPER_ID="Developer ID Application: Your Name (XXXXXXXXXX)" 20 + # APPLE_ID="your@email.com" 21 + # NOTARIZE_PASSWORD="app-specific-password" 18 22 BUNDLE_ID="${BUNDLE_ID:-com.example.plc-touch}" 19 23 TEAM_ID="${TEAM_ID:-XXXXXXXXXX}" 20 24 25 + MODE="${1:-dev}" 26 + 27 + case "$MODE" in 28 + dev) 29 + IDENTITY="${CODESIGN_IDENTITY:?Set CODESIGN_IDENTITY in .env}" 30 + ;; 31 + release) 32 + IDENTITY="${DEVELOPER_ID:?Set DEVELOPER_ID in .env for release builds}" 33 + ;; 34 + *) 35 + echo "Usage: ./build.sh [dev|release]" 36 + echo " dev — Development build with provisioning profile (default)" 37 + echo " release — Developer ID build with notarization for distribution" 38 + exit 1 39 + ;; 40 + esac 41 + 42 + echo "Building plc-touch ($MODE)..." 43 + 21 44 KEYCHAIN_ACCESS_GROUP="${TEAM_ID}.${BUNDLE_ID}" cargo build --release 22 45 23 46 # Create .app bundle ··· 26 49 27 50 cp target/release/plc-touch "$APP/Contents/MacOS/plc-touch" 28 51 29 - # Copy provisioning profile if it exists 30 - if [ -f embedded.provisionprofile ]; then 52 + # Embed provisioning profile 53 + if [ "$MODE" = "dev" ] && [ -f embedded.provisionprofile ]; then 31 54 cp embedded.provisionprofile "$APP/Contents/embedded.provisionprofile" 55 + elif [ "$MODE" = "release" ] && [ -f release.provisionprofile ]; then 56 + cp release.provisionprofile "$APP/Contents/embedded.provisionprofile" 32 57 fi 33 58 34 59 cat > "$APP/Contents/Info.plist" << EOF ··· 54 79 </plist> 55 80 EOF 56 81 57 - # Generate entitlements from env vars 82 + # Generate entitlements 58 83 ENTITLEMENTS_FILE=$(mktemp) 59 - cat > "$ENTITLEMENTS_FILE" << EOF 84 + if [ "$MODE" = "release" ]; then 85 + # Developer ID with provisioning profile: includes application-identifier + keychain-access-groups 86 + cat > "$ENTITLEMENTS_FILE" << EOF 87 + <?xml version="1.0" encoding="UTF-8"?> 88 + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 89 + <plist version="1.0"> 90 + <dict> 91 + <key>com.apple.application-identifier</key> 92 + <string>${TEAM_ID}.${BUNDLE_ID}</string> 93 + <key>keychain-access-groups</key> 94 + <array> 95 + <string>${TEAM_ID}.*</string> 96 + </array> 97 + </dict> 98 + </plist> 99 + EOF 100 + else 101 + # Dev: needs application-identifier for provisioning profile 102 + cat > "$ENTITLEMENTS_FILE" << EOF 60 103 <?xml version="1.0" encoding="UTF-8"?> 61 104 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 62 105 <plist version="1.0"> ··· 70 113 </dict> 71 114 </plist> 72 115 EOF 116 + fi 73 117 74 - codesign --force --sign "$IDENTITY" --entitlements "$ENTITLEMENTS_FILE" "$APP" 75 - rm -f "$ENTITLEMENTS_FILE" 118 + if [ "$MODE" = "release" ]; then 119 + # Release: Developer ID signing with hardened runtime (required for notarization) 120 + codesign --force --sign "$IDENTITY" \ 121 + --options runtime \ 122 + --timestamp \ 123 + --entitlements "$ENTITLEMENTS_FILE" \ 124 + "$APP" 125 + 126 + rm -f "$ENTITLEMENTS_FILE" 127 + 128 + echo "✓ Signed with Developer ID" 129 + 130 + # Create zip for notarization 131 + ZIP="target/release/plc-touch.zip" 132 + rm -f "$ZIP" 133 + ditto -c -k --keepParent "$APP" "$ZIP" 134 + 135 + echo "Submitting for notarization..." 136 + APPLE_ID_ARG="${APPLE_ID:?Set APPLE_ID in .env for notarization}" 137 + PASS_ARG="${NOTARIZE_PASSWORD:?Set NOTARIZE_PASSWORD in .env (app-specific password)}" 138 + 139 + xcrun notarytool submit "$ZIP" \ 140 + --apple-id "$APPLE_ID_ARG" \ 141 + --team-id "$TEAM_ID" \ 142 + --password "$PASS_ARG" \ 143 + --wait 144 + 145 + # Staple the notarization ticket to the app 146 + xcrun stapler staple "$APP" 147 + 148 + # Re-create zip with stapled app 149 + rm -f "$ZIP" 150 + ditto -c -k --keepParent "$APP" "$ZIP" 76 151 77 - echo "✓ Built and signed $APP" 78 - echo " Run with: $APP/Contents/MacOS/plc-touch" 152 + echo "" 153 + echo "✓ Built, signed, notarized, and stapled" 154 + echo " Distribute: $ZIP" 155 + echo " Run with: $APP/Contents/MacOS/plc-touch" 156 + else 157 + # Dev: Apple Development signing 158 + codesign --force --sign "$IDENTITY" \ 159 + --entitlements "$ENTITLEMENTS_FILE" \ 160 + "$APP" 161 + 162 + rm -f "$ENTITLEMENTS_FILE" 163 + 164 + echo "" 165 + echo "✓ Built and signed (dev)" 166 + echo " Run with: $APP/Contents/MacOS/plc-touch" 167 + fi
+1 -1
src/enclave.rs
··· 145 145 CFString::wrap_under_get_rule(kSecAttrSynchronizable), 146 146 CFBoolean::true_value().as_CFType(), 147 147 )); 148 - // Use explicit access group so the key is findable across devices 148 + // Explicit access group so the key is findable across devices 149 149 attrs_pairs.push(( 150 150 CFString::wrap_under_get_rule(kSecAttrAccessGroup), 151 151 CFString::new(keychain_access_group()).as_CFType(),