Skip to content

Commit 8cb9708

Browse files
committed
feat(setup): improve macOS signing UX for non-Mac hosts
- Detect host OS and show platform-appropriate guidance - On Mac: show 'security find-identity' and 'xcrun notarytool' commands - On Linux/Windows: show rcodesign info and P12 certificate path field - Add App Store Connect API fields for cross-platform notarization - Link to Apple docs for API key creation - Disable keychain profile field on non-Mac (not applicable)
1 parent 22c00ef commit 8cb9708

File tree

6 files changed

+187
-73
lines changed

6 files changed

+187
-73
lines changed

v3/internal/setupwizard/frontend/dist/assets/index-BVQMnJjn.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/internal/setupwizard/frontend/dist/assets/index-Cxh5b1cS.js

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/internal/setupwizard/frontend/dist/assets/index-DgfT-RTI.js

Lines changed: 0 additions & 60 deletions
This file was deleted.

v3/internal/setupwizard/frontend/dist/assets/index-Dq6tbWrr.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

v3/internal/setupwizard/frontend/dist/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<link rel="preconnect" href="https://fonts.googleapis.com">
88
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
99
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
10-
<script type="module" crossorigin src="/assets/index-DgfT-RTI.js"></script>
11-
<link rel="stylesheet" crossorigin href="/assets/index-Dq6tbWrr.css">
10+
<script type="module" crossorigin src="/assets/index-Cxh5b1cS.js"></script>
11+
<link rel="stylesheet" crossorigin href="/assets/index-BVQMnJjn.css">
1212
</head>
1313
<body>
1414
<div id="root"></div>

v3/internal/setupwizard/frontend/src/components/SigningStep.tsx

Lines changed: 124 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useEffect, useRef } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
33
import type { SigningStatus, SigningDefaults } from '../types';
4-
import { getSigningStatus, getSigning, saveSigning } from '../api';
4+
import { getSigningStatus, getSigning, saveSigning, getState } from '../api';
55

66
const pageVariants = {
77
initial: { opacity: 0 },
@@ -10,6 +10,7 @@ const pageVariants = {
1010
};
1111

1212
type Platform = 'darwin' | 'windows' | 'linux';
13+
type HostOS = 'darwin' | 'windows' | 'linux';
1314

1415
interface Props {
1516
onNext: () => void;
@@ -22,6 +23,7 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
2223
const [status, setStatus] = useState<SigningStatus | null>(null);
2324
const [config, setConfig] = useState<SigningDefaults | null>(null);
2425
const [loading, setLoading] = useState(true);
26+
const [hostOS, setHostOS] = useState<HostOS>('linux');
2527
const [selectedPlatform, setSelectedPlatform] = useState<Platform>('darwin');
2628
const [configuring, setConfiguring] = useState(false);
2729
const [saving, setSaving] = useState(false);
@@ -34,9 +36,12 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
3436

3537
const loadData = async () => {
3638
try {
37-
const [s, c] = await Promise.all([getSigningStatus(), getSigning()]);
39+
const [s, c, state] = await Promise.all([getSigningStatus(), getSigning(), getState()]);
3840
setStatus(s);
3941
setConfig(c || { darwin: {}, windows: {}, linux: {} });
42+
if (state.system?.os) {
43+
setHostOS(state.system.os as HostOS);
44+
}
4045
} catch (e) {
4146
console.error('Failed to load signing data:', e);
4247
} finally {
@@ -62,8 +67,24 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
6267
if (!config) return null;
6368

6469
if (selectedPlatform === 'darwin') {
70+
const isOnMac = hostOS === 'darwin';
71+
6572
return (
6673
<div className="space-y-4">
74+
{!isOnMac && (
75+
<div className="p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 text-sm">
76+
<p className="text-amber-800 dark:text-amber-200 font-medium mb-1">Cross-platform signing</p>
77+
<p className="text-amber-700 dark:text-amber-300 text-xs">
78+
You can sign macOS apps from {hostOS === 'linux' ? 'Linux' : 'Windows'} using{' '}
79+
<a href="https://github.com/indygreg/apple-platform-rs/tree/main/apple-codesign"
80+
target="_blank"
81+
rel="noopener noreferrer"
82+
className="underline hover:no-underline">rcodesign</a>.
83+
You'll need a .p12 certificate file exported from a Mac.
84+
</p>
85+
</div>
86+
)}
87+
6788
<div>
6889
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
6990
Signing Identity
@@ -78,9 +99,11 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
7899
placeholder="Developer ID Application: Your Name (TEAMID)"
79100
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
80101
/>
81-
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
82-
Find with: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded">security find-identity -v -p codesigning</code>
83-
</p>
102+
{isOnMac && (
103+
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
104+
Find with: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded">security find-identity -v -p codesigning</code>
105+
</p>
106+
)}
84107
</div>
85108

86109
<div>
@@ -99,9 +122,30 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
99122
/>
100123
</div>
101124

125+
{!isOnMac && (
126+
<div>
127+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
128+
P12 Certificate Path
129+
</label>
130+
<input
131+
type="text"
132+
value={config.darwin?.p12Path || ''}
133+
onChange={(e) => setConfig({
134+
...config,
135+
darwin: { ...config.darwin, p12Path: e.target.value }
136+
})}
137+
placeholder="/path/to/certificate.p12"
138+
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
139+
/>
140+
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
141+
Export from Keychain Access on a Mac, or generate via Apple Developer Portal
142+
</p>
143+
</div>
144+
)}
145+
102146
<div>
103147
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
104-
Notarization Profile (optional)
148+
Notarization Profile {!isOnMac && '(Mac only)'}
105149
</label>
106150
<input
107151
type="text"
@@ -111,12 +155,82 @@ export default function SigningStep({ onNext, onSkip, onBack, canGoBack }: Props
111155
darwin: { ...config.darwin, keychainProfile: e.target.value }
112156
})}
113157
placeholder="notarytool-profile"
114-
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
158+
disabled={!isOnMac}
159+
className={`w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500 ${!isOnMac ? 'opacity-50 cursor-not-allowed' : ''}`}
115160
/>
116-
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
117-
Create with: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded">xcrun notarytool store-credentials</code>
118-
</p>
161+
{isOnMac && (
162+
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
163+
Create with: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded">xcrun notarytool store-credentials</code>
164+
</p>
165+
)}
166+
{!isOnMac && (
167+
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
168+
For cross-platform notarization, use App Store Connect API keys instead
169+
</p>
170+
)}
119171
</div>
172+
173+
{!isOnMac && (
174+
<>
175+
<div className="border-t border-gray-200 dark:border-gray-700 pt-4 mt-4">
176+
<p className="text-xs text-gray-500 dark:text-gray-400 mb-3 font-medium">
177+
App Store Connect API (for notarization from {hostOS === 'linux' ? 'Linux' : 'Windows'})
178+
</p>
179+
</div>
180+
<div>
181+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
182+
API Key ID
183+
</label>
184+
<input
185+
type="text"
186+
value={config.darwin?.apiKeyID || ''}
187+
onChange={(e) => setConfig({
188+
...config,
189+
darwin: { ...config.darwin, apiKeyID: e.target.value }
190+
})}
191+
placeholder="ABC123DEF4"
192+
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
193+
/>
194+
</div>
195+
<div>
196+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
197+
Issuer ID
198+
</label>
199+
<input
200+
type="text"
201+
value={config.darwin?.apiIssuerID || ''}
202+
onChange={(e) => setConfig({
203+
...config,
204+
darwin: { ...config.darwin, apiIssuerID: e.target.value }
205+
})}
206+
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
207+
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
208+
/>
209+
</div>
210+
<div>
211+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
212+
API Key Path (.p8 file)
213+
</label>
214+
<input
215+
type="text"
216+
value={config.darwin?.apiKeyPath || ''}
217+
onChange={(e) => setConfig({
218+
...config,
219+
darwin: { ...config.darwin, apiKeyPath: e.target.value }
220+
})}
221+
placeholder="/path/to/AuthKey_ABC123DEF4.p8"
222+
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
223+
/>
224+
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
225+
Create at{' '}
226+
<a href="https://appstoreconnect.apple.com/access/api"
227+
target="_blank"
228+
rel="noopener noreferrer"
229+
className="text-blue-500 hover:underline">App Store Connect → Users and Access → Keys</a>
230+
</p>
231+
</div>
232+
</>
233+
)}
120234
</div>
121235
);
122236
}

0 commit comments

Comments
 (0)