diff --git a/.changes/macos-pkg-installer.md b/.changes/macos-pkg-installer.md new file mode 100644 index 000000000000..bc4cac6ecdd8 --- /dev/null +++ b/.changes/macos-pkg-installer.md @@ -0,0 +1,20 @@ +--- +tauri-bundler: minor:feat +tauri-utils: minor:feat +tauri-cli: minor:feat +--- + +Add macOS PKG installer support with custom signing commands. + +Implements support for creating macOS PKG installers using pkgbuild and productbuild, with native signing via productsign and support for custom signing commands (useful for HSM-based signing solutions). + +Features: +- Create PKG installers from .app bundles using distribution.xml from project root +- Native PKG signing with productsign using signingIdentity or APPLE_CERTIFICATE +- Custom signing command support for .app bundles, .pkg installers, and .dmg disk images +- Custom commands use %1 placeholder for artifact path and run in build directory for relative path support + +Configuration fields added to MacOsSettings: +- `appSignCommand`: Custom command for signing .app bundles +- `pkgSignCommand`: Custom command for signing .pkg installers +- `dmgSignCommand`: Custom command for signing .dmg disk images diff --git a/crates/tauri-bundler/src/bundle.rs b/crates/tauri-bundler/src/bundle.rs index 654ad5647401..6eca9573f531 100644 --- a/crates/tauri-bundler/src/bundle.rs +++ b/crates/tauri-bundler/src/bundle.rs @@ -70,11 +70,15 @@ pub struct Bundle { /// Returns the list of paths where the bundles can be found. pub fn bundle_project(settings: &Settings) -> crate::Result> { let mut package_types = settings.package_types()?; + log::info!("Initial package types: {:?}", package_types); + if package_types.is_empty() { + log::warn!("Package types is empty, returning early"); return Ok(Vec::new()); } package_types.sort_by_key(|a| a.priority()); + log::info!("Sorted package types: {:?}", package_types); let target_os = settings.target_platform(); @@ -110,8 +114,11 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { let mut main_binary_signed = false; let mut bundles = Vec::::new(); for package_type in &package_types { + log::info!("Processing package type: {:?}", package_type); + // bundle was already built! e.g. DMG already built .app if bundles.iter().any(|b| b.package_type == *package_type) { + log::info!("Skipping {:?}, already built", package_type); continue; } @@ -150,6 +157,18 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { } bundled.dmg } + // pkg is dependent of MacOsBundle, we send our bundles to prevent rebuilding + #[cfg(target_os = "macos")] + PackageType::Pkg => { + let bundled = macos::pkg::bundle_project(settings, &bundles)?; + if !bundled.app.is_empty() { + bundles.push(Bundle { + package_type: PackageType::MacOsBundle, + bundle_paths: bundled.app, + }); + } + bundled.pkg + } #[cfg(target_os = "windows")] PackageType::WindowsMsi => windows::msi::bundle_project(settings, false)?, diff --git a/crates/tauri-bundler/src/bundle/macos/app.rs b/crates/tauri-bundler/src/bundle/macos/app.rs index 703973c2b75a..818c68e2ec9d 100644 --- a/crates/tauri-bundler/src/bundle/macos/app.rs +++ b/crates/tauri-bundler/src/bundle/macos/app.rs @@ -106,9 +106,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { if settings.no_sign() { log::warn!("Skipping signing due to --no-sign flag.",); + } else if let Some(app_sign_command) = &settings.macos().app_sign_command { + // Use custom signing command for the .app bundle + // The custom command is responsible for deep signing the contents of the .app + super::sign::sign_app_custom(&app_bundle_path, app_sign_command)?; } else if let Some(keychain) = super::sign::keychain(settings.macos().signing_identity.as_deref())? { + // Use native codesign // Sign frameworks and sidecar binaries first, per apple, signing must be done inside out // https://developer.apple.com/forums/thread/701514 sign_paths.push(SignTarget { diff --git a/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs b/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs index 2c756faeef8a..fb0a6b757d23 100644 --- a/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs +++ b/crates/tauri-bundler/src/bundle/macos/dmg/mod.rs @@ -192,17 +192,25 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result< // Sign DMG if needed // skipping self-signing DMGs https://github.com/tauri-apps/tauri/issues/12288 - let identity = settings.macos().signing_identity.as_deref(); - if !settings.no_sign() && identity != Some("-") { - if let Some(keychain) = super::sign::keychain(identity)? { - super::sign::sign( - &keychain, - vec![super::sign::SignTarget { - path: dmg_path.clone(), - is_an_executable: false, - }], - settings, - )?; + if !settings.no_sign() { + if let Some(dmg_sign_command) = &settings.macos().dmg_sign_command { + // Use custom signing command + super::sign::sign_dmg_custom(&dmg_path, dmg_sign_command)?; + } else { + // Use native codesign + let identity = settings.macos().signing_identity.as_deref(); + if identity != Some("-") { + if let Some(keychain) = super::sign::keychain(identity)? { + super::sign::sign( + &keychain, + vec![super::sign::SignTarget { + path: dmg_path.clone(), + is_an_executable: false, + }], + settings, + )?; + } + } } } diff --git a/crates/tauri-bundler/src/bundle/macos/mod.rs b/crates/tauri-bundler/src/bundle/macos/mod.rs index 26b998d734bc..2629224f8b08 100644 --- a/crates/tauri-bundler/src/bundle/macos/mod.rs +++ b/crates/tauri-bundler/src/bundle/macos/mod.rs @@ -7,4 +7,5 @@ pub mod app; pub mod dmg; pub mod icon; pub mod ios; +pub mod pkg; pub mod sign; diff --git a/crates/tauri-bundler/src/bundle/macos/pkg/mod.rs b/crates/tauri-bundler/src/bundle/macos/pkg/mod.rs new file mode 100644 index 000000000000..d92ec502a806 --- /dev/null +++ b/crates/tauri-bundler/src/bundle/macos/pkg/mod.rs @@ -0,0 +1,133 @@ +// Copyright 2016-2019 Cargo-Bundle developers +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::app; +use crate::{ + bundle::{settings::Arch, Bundle}, + utils::CommandExt, + PackageType, Settings, +}; + +use std::{ + fs, + path::PathBuf, + process::Command, +}; + +pub struct Bundled { + pub pkg: Vec, + pub app: Vec, +} + +/// Bundles the project into a macOS PKG installer. +/// Returns a vector of PathBuf that shows where the PKG was created. +pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result { + // generate the .app bundle if needed + let app_bundle_paths = if !bundles + .iter() + .any(|bundle| bundle.package_type == PackageType::MacOsBundle) + { + app::bundle_project(settings)? + } else { + Vec::new() + }; + + // get the target path + let output_path = settings.project_out_directory().join("bundle/macos"); + let pkg_output_path = output_path.parent().unwrap().join("pkg"); + + fs::create_dir_all(&pkg_output_path)?; + + let package_base_name = format!( + "{}_{}_{}", + settings.product_name(), + settings.version_string(), + match settings.binary_arch() { + Arch::X86_64 => "x64", + Arch::AArch64 => "aarch64", + Arch::Universal => "universal", + target => { + return Err(crate::Error::ArchError(format!( + "Unsupported architecture: {target:?}" + ))); + } + } + ); + + let pkg_name = format!("{}.pkg", &package_base_name); + let pkg_path = pkg_output_path.join(&pkg_name); + + let product_name = settings.product_name(); + let bundle_file_name = format!("{product_name}.app"); + let app_bundle_path = output_path.join(&bundle_file_name); + + log::info!(action = "Bundling"; "{} ({})", pkg_name, pkg_path.display()); + + // Step 1: Create a component package using pkgbuild + // This packages the .app bundle into a component package + let component_pkg_path = pkg_output_path.join("component.pkg"); + + let mut pkgbuild_cmd = Command::new("pkgbuild"); + pkgbuild_cmd + .arg("--component") + .arg(&app_bundle_path) + .arg("--install-location") + .arg("/Applications") + .arg(&component_pkg_path); + + log::info!(action = "Running"; "pkgbuild (component package)"); + pkgbuild_cmd + .output_ok() + .map_err(|e| crate::Error::ShellScriptError(format!("pkgbuild failed: {}", e)))?; + + // Step 2: Read distribution.xml from project root + // User must provide this file for PKG bundling + let distribution_xml_path = std::env::current_dir()?.join("distribution.xml"); + if !distribution_xml_path.exists() { + return Err(crate::Error::GenericError( + "distribution.xml not found in project root. PKG bundling requires a distribution.xml file.".to_string() + )); + } + + log::info!(action = "Using"; "distribution.xml from {}", distribution_xml_path.display()); + + // Step 3: Create the distribution package using productbuild + // This combines the component package(s) into a final installer + let mut productbuild_cmd = Command::new("productbuild"); + productbuild_cmd + .arg("--distribution") + .arg(&distribution_xml_path) + .arg("--package-path") + .arg(&pkg_output_path) + .arg(&pkg_path); + + log::info!(action = "Running"; "productbuild (distribution package)"); + productbuild_cmd + .output_ok() + .map_err(|e| crate::Error::ShellScriptError(format!("productbuild failed: {}", e)))?; + + // Sign PKG if needed + if !settings.no_sign() { + if let Some(pkg_sign_command) = &settings.macos().pkg_sign_command { + // Use custom signing command + super::sign::sign_pkg_custom(&pkg_path, pkg_sign_command)?; + } else { + // Use native productsign + let identity = settings.macos().signing_identity.as_deref(); + if identity != Some("-") { + if let Some(identity) = identity { + super::sign::sign_pkg(&pkg_path, identity, settings)?; + } + } + } + } + + log::info!(action = "Finished"; "PKG installer at {}", pkg_path.display()); + + Ok(Bundled { + pkg: vec![pkg_path], + app: app_bundle_paths, + }) +} diff --git a/crates/tauri-bundler/src/bundle/macos/sign.rs b/crates/tauri-bundler/src/bundle/macos/sign.rs index 8d8bf6c2b5ea..f338b6cafc3b 100644 --- a/crates/tauri-bundler/src/bundle/macos/sign.rs +++ b/crates/tauri-bundler/src/bundle/macos/sign.rs @@ -167,3 +167,126 @@ fn find_api_key(folder: PathBuf, file_name: &OsString) -> Option { None } } + +/// Sign a PKG installer using productsign +pub fn sign_pkg( + pkg_path: &std::path::Path, + identity: &str, + settings: &Settings, +) -> crate::Result<()> { + use std::process::Command; + use crate::utils::CommandExt; + + log::info!(action = "Signing"; "PKG with identity \"{}\"", identity); + + // Create a temporary path for the signed package + let signed_pkg_path = pkg_path.with_extension("signed.pkg"); + + // Run productsign to sign the package + let mut cmd = Command::new("productsign"); + cmd + .arg("--sign") + .arg(identity) + .arg(pkg_path) + .arg(&signed_pkg_path); + + cmd.output_ok().map_err(|e| { + crate::Error::GenericError(format!("Failed to sign PKG with productsign: {}", e)) + })?; + + // Replace the unsigned package with the signed one + std::fs::rename(&signed_pkg_path, pkg_path)?; + + log::info!(action = "Signed"; "PKG at {}", pkg_path.display()); + + Ok(()) +} + +/// Sign a PKG installer using a custom command +pub fn sign_pkg_custom( + pkg_path: &std::path::Path, + command: &crate::bundle::settings::CustomSignCommandSettings, +) -> crate::Result<()> { + use crate::utils::CommandExt; + + log::info!(action = "Signing"; "PKG with custom command"); + + let mut cmd = sign_command_custom(pkg_path, command)?; + let output = cmd.output_ok()?; + + let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned(); + log::info!(action = "Signing"; "Output of signing command:\n{}", stdout.trim()); + + Ok(()) +} + +/// Sign a DMG disk image using a custom command +pub fn sign_dmg_custom( + dmg_path: &std::path::Path, + command: &crate::bundle::settings::CustomSignCommandSettings, +) -> crate::Result<()> { + use crate::utils::CommandExt; + + log::info!(action = "Signing"; "DMG with custom command"); + + let mut cmd = sign_command_custom(dmg_path, command)?; + let output = cmd.output_ok()?; + + let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned(); + log::info!(action = "Signing"; "Output of signing command:\n{}", stdout.trim()); + + Ok(()) +} + +/// Sign an app bundle using a custom command +pub fn sign_app_custom( + app_path: &std::path::Path, + command: &crate::bundle::settings::CustomSignCommandSettings, +) -> crate::Result<()> { + use crate::utils::CommandExt; + + log::info!(action = "Signing"; ".app bundle with custom command"); + + let mut cmd = sign_command_custom(app_path, command)?; + let output = cmd.output_ok()?; + + let stdout = String::from_utf8_lossy(output.stdout.as_slice()).into_owned(); + log::info!(action = "Signing"; "Output of signing command:\n{}", stdout.trim()); + + Ok(()) +} + +/// Build a custom signing command with %1 placeholder substitution +fn sign_command_custom>( + path: P, + command: &crate::bundle::settings::CustomSignCommandSettings, +) -> crate::Result { + use std::path::Path; + + let path = path.as_ref(); + let cwd = std::env::current_dir()?; + + let mut cmd = std::process::Command::new(&command.cmd); + + // Set the current working directory to where the command is expected to run from + // This allows relative paths in the cmd field to work correctly + cmd.current_dir(&cwd); + + for arg in &command.args { + if arg == "%1" { + cmd.arg(path); + } else { + let arg_path = Path::new(arg); + // turn relative paths into absolute paths + if arg_path.exists() && arg_path.is_relative() { + cmd.arg(cwd.join(arg_path)); + } else { + cmd.arg(arg); + } + } + } + + log::info!(action = "Signing"; "Running command from directory: {}", cwd.display()); + + Ok(cmd) +} diff --git a/crates/tauri-bundler/src/bundle/settings.rs b/crates/tauri-bundler/src/bundle/settings.rs index 62f7813d2cdd..b61bc6e5a707 100644 --- a/crates/tauri-bundler/src/bundle/settings.rs +++ b/crates/tauri-bundler/src/bundle/settings.rs @@ -40,6 +40,8 @@ pub enum PackageType { AppImage, /// The macOS DMG bundle (.dmg). Dmg, + /// The macOS PKG installer (.pkg). + Pkg, /// The Updater bundle. Updater, } @@ -60,7 +62,7 @@ impl From for PackageType { impl PackageType { /// Maps a short name to a PackageType. - /// Possible values are "deb", "ios", "msi", "app", "rpm", "appimage", "dmg", "updater". + /// Possible values are "deb", "ios", "msi", "app", "rpm", "appimage", "dmg", "pkg", "updater". pub fn from_short_name(name: &str) -> Option { // Other types we may eventually want to support: apk. match name { @@ -72,6 +74,7 @@ impl PackageType { "rpm" => Some(PackageType::Rpm), "appimage" => Some(PackageType::AppImage), "dmg" => Some(PackageType::Dmg), + "pkg" => Some(PackageType::Pkg), "updater" => Some(PackageType::Updater), _ => None, } @@ -89,6 +92,7 @@ impl PackageType { PackageType::Rpm => "rpm", PackageType::AppImage => "appimage", PackageType::Dmg => "dmg", + PackageType::Pkg => "pkg", PackageType::Updater => "updater", } } @@ -114,6 +118,7 @@ impl PackageType { PackageType::Rpm => 0, PackageType::AppImage => 0, PackageType::Dmg => 1, + PackageType::Pkg => 1, PackageType::Updater => 2, } } @@ -134,6 +139,8 @@ const ALL_PACKAGE_TYPES: &[PackageType] = &[ PackageType::Rpm, #[cfg(target_os = "macos")] PackageType::Dmg, + #[cfg(target_os = "macos")] + PackageType::Pkg, #[cfg(target_os = "linux")] PackageType::AppImage, PackageType::Updater, @@ -364,6 +371,38 @@ pub struct MacOsSettings { pub entitlements: Option, /// Path to the Info.plist file or raw plist value to merge with the bundle Info.plist. pub info_plist: Option, + /// Specify a custom command to sign the .app bundle. + /// This command needs to have a `%1` in it which is just a placeholder for the .app bundle path. + /// The custom command is responsible for signing everything inside the bundle. + /// + /// Example: + /// ```text + /// custom-sign --app %1 --output-dir signed/ + /// ``` + /// + /// If this is set, it will be used instead of the native codesign process. + /// By Default we use `codesign` which can be found only on macOS. + pub app_sign_command: Option, + /// Specify a custom command to sign the .pkg installer. + /// This command needs to have a `%1` in it which is just a placeholder for the PKG path. + /// + /// Example: + /// ```text + /// custom-sign --pkg %1 --output-dir signed/ + /// ``` + /// + /// By Default we use `productsign` which can be found only on macOS. + pub pkg_sign_command: Option, + /// Specify a custom command to sign the .dmg disk image. + /// This command needs to have a `%1` in it which is just a placeholder for the DMG path. + /// + /// Example: + /// ```text + /// custom-sign --dmg %1 --output-dir signed/ + /// ``` + /// + /// By Default we use `codesign` which can be found only on macOS. + pub dmg_sign_command: Option, } /// Entitlements for macOS code signing. @@ -1046,7 +1085,7 @@ impl Settings { let target_os = self.target_platform(); let platform_types = match target_os { - TargetPlatform::MacOS => vec![PackageType::MacOsBundle, PackageType::Dmg], + TargetPlatform::MacOS => vec![PackageType::MacOsBundle, PackageType::Dmg, PackageType::Pkg], TargetPlatform::Ios => vec![PackageType::IosBundle], TargetPlatform::Linux => vec![PackageType::Deb, PackageType::Rpm, PackageType::AppImage], TargetPlatform::Windows => vec![PackageType::WindowsMsi, PackageType::Nsis], diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index 1d19c20eaede..c9734e758ca8 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -3626,6 +3626,39 @@ "null" ] }, + "appSignCommand": { + "description": "Specify a custom command to sign the .app bundle.\n This command needs to have a `%1` in args which is just a placeholder for the .app bundle path.\n\n The custom command is responsible for deep signing the contents of the .app.\n If this is set, it will be used instead of the native codesign process.", + "anyOf": [ + { + "$ref": "#/definitions/CustomSignCommandConfig" + }, + { + "type": "null" + } + ] + }, + "pkgSignCommand": { + "description": "Specify a custom command to sign the .pkg installer.\n This command needs to have a `%1` in args which is just a placeholder for the .pkg path.\n\n By Default we use `productsign` which can be found only on macOS.", + "anyOf": [ + { + "$ref": "#/definitions/CustomSignCommandConfig" + }, + { + "type": "null" + } + ] + }, + "dmgSignCommand": { + "description": "Specify a custom command to sign the .dmg disk image.\n This command needs to have a `%1` in args which is just a placeholder for the .dmg path.\n\n By Default we use `codesign` which can be found only on macOS.", + "anyOf": [ + { + "$ref": "#/definitions/CustomSignCommandConfig" + }, + { + "type": "null" + } + ] + }, "dmg": { "description": "DMG-specific settings.", "default": { diff --git a/crates/tauri-cli/src/bundle.rs b/crates/tauri-cli/src/bundle.rs index 890386cd71fc..95a0671bde74 100644 --- a/crates/tauri-cli/src/bundle.rs +++ b/crates/tauri-cli/src/bundle.rs @@ -186,13 +186,17 @@ pub fn bundle( .collect() }; + log::debug!("Package types to bundle: {:?}", package_types); + if package_types.is_empty() { + log::warn!("No package types specified, exiting bundle command"); return Ok(()); } // if we have a package to bundle, let's run the `before_bundle_command`. if !package_types.is_empty() { if let Some(before_bundle) = config.build.before_bundle_command.clone() { + log::debug!("Running beforeBundleCommand"); helpers::run_hook( "beforeBundleCommand", before_bundle, @@ -202,6 +206,7 @@ pub fn bundle( } } + log::debug!("Getting bundler settings"); let mut settings = app_settings .get_bundler_settings(options.clone().into(), config, out_dir, package_types) .with_context(|| "failed to build bundler settings")?; @@ -213,8 +218,11 @@ pub fn bundle( _ => log::Level::Trace, }); + log::debug!("Calling tauri_bundler::bundle_project"); let bundles = tauri_bundler::bundle_project(&settings).map_err(Box::new)?; + log::debug!("Bundle project completed, bundles: {:?}", bundles); + sign_updaters(settings, bundles, ci)?; Ok(()) diff --git a/crates/tauri-cli/src/interface/rust.rs b/crates/tauri-cli/src/interface/rust.rs index 0f7d2fd02973..2671fa4f6516 100644 --- a/crates/tauri-cli/src/interface/rust.rs +++ b/crates/tauri-cli/src/interface/rust.rs @@ -1582,6 +1582,9 @@ fn tauri_config_to_bundle_settings( crate::helpers::plist::merge_plist(src_plists)?, )) }, + app_sign_command: config.macos.app_sign_command.map(custom_sign_settings), + pkg_sign_command: config.macos.pkg_sign_command.map(custom_sign_settings), + dmg_sign_command: config.macos.dmg_sign_command.map(custom_sign_settings), }, windows: WindowsSettings { timestamp_url: config.windows.timestamp_url, diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index 1d19c20eaede..c9734e758ca8 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -3626,6 +3626,39 @@ "null" ] }, + "appSignCommand": { + "description": "Specify a custom command to sign the .app bundle.\n This command needs to have a `%1` in args which is just a placeholder for the .app bundle path.\n\n The custom command is responsible for deep signing the contents of the .app.\n If this is set, it will be used instead of the native codesign process.", + "anyOf": [ + { + "$ref": "#/definitions/CustomSignCommandConfig" + }, + { + "type": "null" + } + ] + }, + "pkgSignCommand": { + "description": "Specify a custom command to sign the .pkg installer.\n This command needs to have a `%1` in args which is just a placeholder for the .pkg path.\n\n By Default we use `productsign` which can be found only on macOS.", + "anyOf": [ + { + "$ref": "#/definitions/CustomSignCommandConfig" + }, + { + "type": "null" + } + ] + }, + "dmgSignCommand": { + "description": "Specify a custom command to sign the .dmg disk image.\n This command needs to have a `%1` in args which is just a placeholder for the .dmg path.\n\n By Default we use `codesign` which can be found only on macOS.", + "anyOf": [ + { + "$ref": "#/definitions/CustomSignCommandConfig" + }, + { + "type": "null" + } + ] + }, "dmg": { "description": "DMG-specific settings.", "default": { diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index 37c77abff8ac..83a6b7048597 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -664,6 +664,25 @@ pub struct MacConfig { /// Note that Tauri also looks for a `Info.plist` file in the same directory as the Tauri configuration file. #[serde(alias = "info-plist")] pub info_plist: Option, + /// Specify a custom command to sign the .app bundle. + /// This command needs to have a `%1` in args which is just a placeholder for the .app bundle path. + /// + /// The custom command is responsible for deep signing the contents of the .app. + /// If this is set, it will be used instead of the native codesign process. + #[serde(alias = "app-sign-command")] + pub app_sign_command: Option, + /// Specify a custom command to sign the .pkg installer. + /// This command needs to have a `%1` in args which is just a placeholder for the .pkg path. + /// + /// By Default we use `productsign` which can be found only on macOS. + #[serde(alias = "pkg-sign-command")] + pub pkg_sign_command: Option, + /// Specify a custom command to sign the .dmg disk image. + /// This command needs to have a `%1` in args which is just a placeholder for the .dmg path. + /// + /// By Default we use `codesign` which can be found only on macOS. + #[serde(alias = "dmg-sign-command")] + pub dmg_sign_command: Option, /// DMG-specific settings. #[serde(default)] pub dmg: DmgConfig, @@ -683,6 +702,9 @@ impl Default for MacConfig { provider_short_name: None, entitlements: None, info_plist: None, + app_sign_command: None, + pkg_sign_command: None, + dmg_sign_command: None, dmg: Default::default(), } } diff --git a/examples/api/src-tauri/tauri.conf.json b/examples/api/src-tauri/tauri.conf.json index 936e73af4acb..c84fc71f6526 100644 --- a/examples/api/src-tauri/tauri.conf.json +++ b/examples/api/src-tauri/tauri.conf.json @@ -83,6 +83,16 @@ "../../.icons/icon.icns", "../../.icons/icon.ico" ], + "macOS": { + "appSignCommand": { + "cmd": "./shims/sign_app_mock.sh", + "args": ["%1"] + }, + "pkgSignCommand": { + "cmd": "./shims/sign_pkg_mock.sh", + "args": ["%1"] + } + }, "windows": { "wix": { "language": {