anodizer_core/config/notarize.rs
1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use super::{HumanDuration, StringOrBool, deserialize_string_or_bool_opt};
5
6// ---------------------------------------------------------------------------
7// NotarizeConfig (macOS code signing and notarization)
8// ---------------------------------------------------------------------------
9
10/// Top-level notarization configuration supporting both cross-platform
11/// (`rcodesign`) and native macOS (`codesign` + `xcrun notarytool`) modes.
12#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
13#[serde(default, deny_unknown_fields)]
14pub struct NotarizeConfig {
15 /// Skip all notarization. Accepts bool or template string.
16 #[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
17 pub skip: Option<StringOrBool>,
18 /// Cross-platform signing/notarization (rcodesign-based, works on any OS).
19 pub macos: Option<Vec<MacOSSignNotarizeConfig>>,
20 /// Native signing/notarization (codesign + xcrun, macOS only).
21 pub macos_native: Option<Vec<MacOSNativeSignNotarizeConfig>>,
22}
23
24/// Cross-platform macOS signing and notarization via `rcodesign`.
25#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
26#[serde(default, deny_unknown_fields)]
27pub struct MacOSSignNotarizeConfig {
28 /// Build IDs to filter. Default: project name.
29 pub ids: Option<Vec<String>>,
30 /// Skip this configuration. Accepts bool or template string.
31 /// Replaces the previous `enabled:` toggle with the canonical
32 /// `skip:` (inverted semantic) to align with every other publisher /
33 /// pipe in anodizer.
34 #[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
35 pub skip: Option<StringOrBool>,
36 /// Signing configuration (P12 certificate).
37 pub sign: Option<MacOSSignConfig>,
38 /// Notarization configuration (App Store Connect API key). Omit for sign-only.
39 pub notarize: Option<MacOSNotarizeApiConfig>,
40}
41
42/// P12-certificate signing configuration for `rcodesign sign`.
43#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
44#[serde(default)]
45pub struct MacOSSignConfig {
46 /// Path to .p12 certificate file or base64-encoded contents. Templates allowed.
47 pub certificate: Option<String>,
48 /// Password for the .p12 certificate. Templates allowed.
49 pub password: Option<String>,
50 /// Path to entitlements XML file. Templates allowed.
51 pub entitlements: Option<String>,
52 /// RFC-3161 timestamp service URL passed to `rcodesign sign --timestamp-url`.
53 /// Defaults to Apple's public timestamp service. Override when running
54 /// behind a corporate proxy or when Apple's service is unreachable.
55 pub timestamp_url: Option<String>,
56}
57
58impl MacOSSignConfig {
59 /// Apple's public RFC-3161 timestamp service. Used so the signature
60 /// carries a trusted timestamp rather than the host clock; override via
61 /// `notarize.macos[*].sign.timestamp_url` when running behind a corporate
62 /// proxy or when Apple's service is unreachable.
63 pub const DEFAULT_TIMESTAMP_URL: &'static str = "http://timestamp.apple.com/ts01";
64
65 /// Resolve the timestamp URL, ignoring whitespace-only overrides and
66 /// falling back to [`Self::DEFAULT_TIMESTAMP_URL`].
67 pub fn resolved_timestamp_url(&self) -> &str {
68 self.timestamp_url
69 .as_deref()
70 .map(|u| u.trim())
71 .filter(|u| !u.is_empty())
72 .unwrap_or(Self::DEFAULT_TIMESTAMP_URL)
73 }
74}
75
76/// App Store Connect API key configuration for `rcodesign notary-submit`.
77#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
78#[serde(default)]
79pub struct MacOSNotarizeApiConfig {
80 /// App Store Connect API key issuer UUID. Templates allowed.
81 pub issuer_id: Option<String>,
82 /// Path to .p8 key file or base64-encoded contents. Templates allowed.
83 pub key: Option<String>,
84 /// API key ID. Templates allowed.
85 pub key_id: Option<String>,
86 /// Timeout for notarization status polling. Humantime-style string
87 /// (e.g. `"10m"`, `"15s"`, `"1h"`). Default when omitted: `"10m"`.
88 pub timeout: Option<HumanDuration>,
89 /// Whether to wait for notarization to complete.
90 pub wait: Option<bool>,
91}
92
93impl MacOSNotarizeApiConfig {
94 /// Default notarization wait window. Mirrors GoReleaser
95 /// `internal/pipe/notary/macos.go` (`n.Notarize.Timeout = 10 * time.Minute`).
96 pub const DEFAULT_TIMEOUT: &'static str = "10m";
97
98 /// Resolve `wait`, falling back to `false` (don't block on notary).
99 pub fn resolved_wait(&self) -> bool {
100 self.wait.unwrap_or(false)
101 }
102
103 /// Resolve `timeout` as a humantime string, falling back to
104 /// [`Self::DEFAULT_TIMEOUT`]. Returns an owned `String` because the
105 /// stored representation (`HumanDuration`) needs to be re-serialized
106 /// when materializing — there's no zero-cost view into it.
107 pub fn resolved_timeout(&self) -> String {
108 self.timeout
109 .map(|d| d.as_humantime_string())
110 .unwrap_or_else(|| Self::DEFAULT_TIMEOUT.to_string())
111 }
112}
113
114/// Artifact-type selector for native macOS notarization. Constrains the YAML
115/// `use:` field on `notarize.macos_native` so an unsupported value fails at
116/// parse time. Only `dmg` and `pkg` are valid — `notarytool` (the only
117/// supported tool) is implicit.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
119#[serde(rename_all = "snake_case")]
120pub enum MacOSNativeArtifactKind {
121 Dmg,
122 Pkg,
123}
124
125/// Native macOS signing and notarization via `codesign` + `xcrun notarytool`.
126#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
127#[serde(default, deny_unknown_fields)]
128pub struct MacOSNativeSignNotarizeConfig {
129 /// Build IDs to filter. Default: project name.
130 pub ids: Option<Vec<String>>,
131 /// Skip this configuration. Accepts bool or template string.
132 /// Replaces `enabled:` with the canonical `skip:`.
133 #[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
134 pub skip: Option<StringOrBool>,
135 /// Artifact type to sign and notarize: `dmg` (default) or `pkg`.
136 ///
137 /// Anodizer-original. GR's notarize.macos has no equivalent (signs
138 /// binaries directly via rcodesign). Constrained to a typed enum at
139 /// parse time so an unsupported value (`zip`, `app`, etc.) fails fast
140 /// instead of producing a silent no-op signing pipe.
141 #[serde(rename = "use")]
142 pub use_: Option<MacOSNativeArtifactKind>,
143 /// Native signing configuration (Keychain).
144 pub sign: Option<MacOSNativeSignConfig>,
145 /// Native notarization configuration (xcrun notarytool).
146 pub notarize: Option<MacOSNativeNotarizeConfig>,
147}
148
149impl MacOSNativeSignNotarizeConfig {
150 /// Default `use:` selector. Anodize-original — GR has no native
151 /// notarize. DMG is the canonical signed-app distribution format
152 /// for macOS releases; PKG opt-in handles installers.
153 pub const DEFAULT_USE: MacOSNativeArtifactKind = MacOSNativeArtifactKind::Dmg;
154
155 /// Resolve the `use:` selector, falling back to [`Self::DEFAULT_USE`].
156 pub fn resolved_use(&self) -> MacOSNativeArtifactKind {
157 self.use_.unwrap_or(Self::DEFAULT_USE)
158 }
159}
160
161/// Keychain-based signing configuration for native `codesign`.
162#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
163#[serde(default)]
164pub struct MacOSNativeSignConfig {
165 /// Keychain identity (e.g., "Developer ID Application: Name"). Templates allowed.
166 pub identity: Option<String>,
167 /// Path to Keychain file. Templates allowed.
168 pub keychain: Option<String>,
169 /// Options to pass to codesign (e.g., ["runtime"]). Only used for DMGs.
170 pub options: Option<Vec<String>>,
171 /// Path to entitlements XML file. Only used for DMGs. Templates allowed.
172 pub entitlements: Option<String>,
173}
174
175/// Native notarization configuration for `xcrun notarytool`.
176#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
177#[serde(default)]
178pub struct MacOSNativeNotarizeConfig {
179 /// Notarytool stored credentials profile name. Templates allowed.
180 pub profile_name: Option<String>,
181 /// Whether to wait for notarization to complete.
182 pub wait: Option<bool>,
183 /// Timeout for `xcrun notarytool submit --timeout`. Humantime-style
184 /// string (e.g. `"10m"`, `"15s"`, `"1h"`).
185 pub timeout: Option<HumanDuration>,
186}
187
188impl MacOSNativeNotarizeConfig {
189 /// Default notarization wait window. Aligns with the cross-platform
190 /// rcodesign path (and GoReleaser `macos.go`'s `10 * time.Minute`).
191 pub const DEFAULT_TIMEOUT: &'static str = "10m";
192
193 /// Resolve `wait`, falling back to `false`. The native xcrun path
194 /// prints a "submit only" message instead of polling when `wait`
195 /// is false; the unwrap at this accessor pins that fallback in one
196 /// place.
197 pub fn resolved_wait(&self) -> bool {
198 self.wait.unwrap_or(false)
199 }
200
201 /// Resolve `timeout` as a humantime string, falling back to
202 /// [`Self::DEFAULT_TIMEOUT`].
203 pub fn resolved_timeout(&self) -> String {
204 self.timeout
205 .map(|d| d.as_humantime_string())
206 .unwrap_or_else(|| Self::DEFAULT_TIMEOUT.to_string())
207 }
208}