Skip to main content

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}