mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-25 19:54:07 +03:00
feat!(core): add bundle createUpdaterArtifacts configuration (#9883)
* Add updater field * Don't sign updaters when updater field is false * Clippy * Add updater to bundle migration * Format * Add updater config to api example * No warning if update is not enabled * Build * Add change file * We don't generate updater for dmg package * Warning only for v1 compatible * clean up * More clean up * little bit more * Apply suggestions from code review Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com> * Revert license header change * Remove option around pubkey and msi args * More migration tests * Refactor private_key getter * Only generate signature for updater for v1 compat * Format * Use map_err instead of anyhow context * Don't generate updater for api example * Fix misaligned comment * Rename `updater` to `createUpdaterArtifacts` * Revert changes in helloworld example * Add warning for v1 compatible * Update .changes/separate-updater-field.md Co-authored-by: Lucas Nogueira <118899497+lucasfernog-crabnebula@users.noreply.github.com> * update error messages [skip ci] --------- Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com> Co-authored-by: Lucas Nogueira <118899497+lucasfernog-crabnebula@users.noreply.github.com> Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
parent
e7fd7c60d6
commit
911242f092
8
.changes/separate-updater-field.md
Normal file
8
.changes/separate-updater-field.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"tauri-utils": patch:breaking
|
||||||
|
"tauri-bundler": patch:breaking
|
||||||
|
"tauri-cli": patch:breaking
|
||||||
|
"@tauri-apps/cli": patch:breaking
|
||||||
|
---
|
||||||
|
|
||||||
|
Move updater target from `bundle > targets` to a separate field `bundle > createUpdaterArtifacts`
|
@ -72,6 +72,7 @@
|
|||||||
"android": {
|
"android": {
|
||||||
"minSdkVersion": 24
|
"minSdkVersion": 24
|
||||||
},
|
},
|
||||||
|
"createUpdaterArtifacts": false,
|
||||||
"iOS": {},
|
"iOS": {},
|
||||||
"icon": [],
|
"icon": [],
|
||||||
"linux": {
|
"linux": {
|
||||||
@ -1519,7 +1520,7 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"targets": {
|
"targets": {
|
||||||
"description": "The bundle targets, currently supports [\"deb\", \"rpm\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\", \"updater\"] or \"all\".",
|
"description": "The bundle targets, currently supports [\"deb\", \"rpm\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\"] or \"all\".",
|
||||||
"default": "all",
|
"default": "all",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
@ -1527,6 +1528,15 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"createUpdaterArtifacts": {
|
||||||
|
"description": "Produce updaters and their signatures or not",
|
||||||
|
"default": false,
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/Updater"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"publisher": {
|
"publisher": {
|
||||||
"description": "The application's publisher. Defaults to the second element in the identifier string.\n Currently maps to the Manufacturer property of the Windows Installer.",
|
"description": "The application's publisher. Defaults to the second element in the identifier string.\n Currently maps to the Manufacturer property of the Windows Installer.",
|
||||||
"type": [
|
"type": [
|
||||||
@ -1794,12 +1804,34 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"dmg"
|
"dmg"
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Updater": {
|
||||||
|
"description": "Updater type",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"description": "Generates lagacy zipped v1 compatible updaters",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/V1Compatible"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "The Tauri updater bundle.",
|
"description": "Produce updaters and their signatures or not",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"V1Compatible": {
|
||||||
|
"description": "Generates lagacy zipped v1 compatible updaters",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "Generates lagacy zipped v1 compatible updaters",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"updater"
|
"v1Compatible"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -116,8 +116,6 @@ pub enum BundleType {
|
|||||||
App,
|
App,
|
||||||
/// The Apple Disk Image bundle (.dmg).
|
/// The Apple Disk Image bundle (.dmg).
|
||||||
Dmg,
|
Dmg,
|
||||||
/// The Tauri updater bundle.
|
|
||||||
Updater,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BundleType {
|
impl BundleType {
|
||||||
@ -131,7 +129,6 @@ impl BundleType {
|
|||||||
BundleType::Nsis,
|
BundleType::Nsis,
|
||||||
BundleType::App,
|
BundleType::App,
|
||||||
BundleType::Dmg,
|
BundleType::Dmg,
|
||||||
BundleType::Updater,
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,7 +146,6 @@ impl Display for BundleType {
|
|||||||
Self::Nsis => "nsis",
|
Self::Nsis => "nsis",
|
||||||
Self::App => "app",
|
Self::App => "app",
|
||||||
Self::Dmg => "dmg",
|
Self::Dmg => "dmg",
|
||||||
Self::Updater => "updater",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -178,7 +174,6 @@ impl<'de> Deserialize<'de> for BundleType {
|
|||||||
"nsis" => Ok(Self::Nsis),
|
"nsis" => Ok(Self::Nsis),
|
||||||
"app" => Ok(Self::App),
|
"app" => Ok(Self::App),
|
||||||
"dmg" => Ok(Self::Dmg),
|
"dmg" => Ok(Self::Dmg),
|
||||||
"updater" => Ok(Self::Updater),
|
|
||||||
_ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
|
_ => Err(DeError::custom(format!("unknown bundle target '{s}'"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1070,6 +1065,33 @@ impl BundleResources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updater type
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||||
|
#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
|
||||||
|
pub enum Updater {
|
||||||
|
/// Generates lagacy zipped v1 compatible updaters
|
||||||
|
String(V1Compatible),
|
||||||
|
/// Produce updaters and their signatures or not
|
||||||
|
// Can't use untagged on enum field here: https://github.com/GREsau/schemars/issues/222
|
||||||
|
Bool(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Updater {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Bool(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates lagacy zipped v1 compatible updaters
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||||
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
|
pub enum V1Compatible {
|
||||||
|
/// Generates lagacy zipped v1 compatible updaters
|
||||||
|
V1Compatible,
|
||||||
|
}
|
||||||
|
|
||||||
/// Configuration for tauri-bundler.
|
/// Configuration for tauri-bundler.
|
||||||
///
|
///
|
||||||
/// See more: <https://tauri.app/v1/api/config#bundleconfig>
|
/// See more: <https://tauri.app/v1/api/config#bundleconfig>
|
||||||
@ -1081,9 +1103,12 @@ pub struct BundleConfig {
|
|||||||
/// Whether Tauri should bundle your application or just output the executable.
|
/// Whether Tauri should bundle your application or just output the executable.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
/// The bundle targets, currently supports ["deb", "rpm", "appimage", "nsis", "msi", "app", "dmg", "updater"] or "all".
|
/// The bundle targets, currently supports ["deb", "rpm", "appimage", "nsis", "msi", "app", "dmg"] or "all".
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub targets: BundleTarget,
|
pub targets: BundleTarget,
|
||||||
|
#[serde(default)]
|
||||||
|
/// Produce updaters and their signatures or not
|
||||||
|
pub create_updater_artifacts: Updater,
|
||||||
/// The application's publisher. Defaults to the second element in the identifier string.
|
/// The application's publisher. Defaults to the second element in the identifier string.
|
||||||
/// Currently maps to the Manufacturer property of the Windows Installer.
|
/// Currently maps to the Manufacturer property of the Windows Installer.
|
||||||
pub publisher: Option<String>,
|
pub publisher: Option<String>,
|
||||||
@ -2474,6 +2499,7 @@ mod build {
|
|||||||
let icon = vec_lit(&self.icon, str_lit);
|
let icon = vec_lit(&self.icon, str_lit);
|
||||||
let active = self.active;
|
let active = self.active;
|
||||||
let targets = quote!(Default::default());
|
let targets = quote!(Default::default());
|
||||||
|
let create_updater_artifacts = quote!(Default::default());
|
||||||
let resources = quote!(None);
|
let resources = quote!(None);
|
||||||
let copyright = quote!(None);
|
let copyright = quote!(None);
|
||||||
let category = quote!(None);
|
let category = quote!(None);
|
||||||
@ -2497,6 +2523,7 @@ mod build {
|
|||||||
homepage,
|
homepage,
|
||||||
icon,
|
icon,
|
||||||
targets,
|
targets,
|
||||||
|
create_updater_artifacts,
|
||||||
resources,
|
resources,
|
||||||
copyright,
|
copyright,
|
||||||
category,
|
category,
|
||||||
@ -2811,6 +2838,7 @@ mod test {
|
|||||||
let bundle = BundleConfig {
|
let bundle = BundleConfig {
|
||||||
active: false,
|
active: false,
|
||||||
targets: Default::default(),
|
targets: Default::default(),
|
||||||
|
create_updater_artifacts: Default::default(),
|
||||||
publisher: None,
|
publisher: None,
|
||||||
homepage: None,
|
homepage: None,
|
||||||
icon: Vec::new(),
|
icon: Vec::new(),
|
||||||
|
@ -42,7 +42,7 @@ pub struct Bundle {
|
|||||||
|
|
||||||
/// Bundles the project.
|
/// Bundles the project.
|
||||||
/// Returns the list of paths where the bundles can be found.
|
/// Returns the list of paths where the bundles can be found.
|
||||||
pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
|
||||||
let mut package_types = settings.package_types()?;
|
let mut package_types = settings.package_types()?;
|
||||||
if package_types.is_empty() {
|
if package_types.is_empty() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
@ -50,8 +50,6 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
|||||||
|
|
||||||
package_types.sort_by_key(|a| a.priority());
|
package_types.sort_by_key(|a| a.priority());
|
||||||
|
|
||||||
let mut bundles: Vec<Bundle> = Vec::new();
|
|
||||||
|
|
||||||
let target_os = settings
|
let target_os = settings
|
||||||
.target()
|
.target()
|
||||||
.split('-')
|
.split('-')
|
||||||
@ -68,7 +66,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
|||||||
if settings.can_sign() {
|
if settings.can_sign() {
|
||||||
for bin in settings.binaries() {
|
for bin in settings.binaries() {
|
||||||
let bin_path = settings.binary_path(bin);
|
let bin_path = settings.binary_path(bin);
|
||||||
windows::sign::try_sign(&bin_path, &settings)?;
|
windows::sign::try_sign(&bin_path, settings)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign the sidecar binaries
|
// Sign the sidecar binaries
|
||||||
@ -89,7 +87,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
windows::sign::try_sign(&path, &settings)?;
|
windows::sign::try_sign(&path, settings)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
@ -97,6 +95,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut bundles = Vec::<Bundle>::new();
|
||||||
for package_type in &package_types {
|
for package_type in &package_types {
|
||||||
// bundle was already built! e.g. DMG already built .app
|
// bundle was already built! e.g. DMG already built .app
|
||||||
if bundles.iter().any(|b| b.package_type == *package_type) {
|
if bundles.iter().any(|b| b.package_type == *package_type) {
|
||||||
@ -105,13 +104,13 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
|||||||
|
|
||||||
let bundle_paths = match package_type {
|
let bundle_paths = match package_type {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
PackageType::MacOsBundle => macos::app::bundle_project(&settings)?,
|
PackageType::MacOsBundle => macos::app::bundle_project(settings)?,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
PackageType::IosBundle => macos::ios::bundle_project(&settings)?,
|
PackageType::IosBundle => macos::ios::bundle_project(settings)?,
|
||||||
// dmg is dependent of MacOsBundle, we send our bundles to prevent rebuilding
|
// dmg is dependent of MacOsBundle, we send our bundles to prevent rebuilding
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
PackageType::Dmg => {
|
PackageType::Dmg => {
|
||||||
let bundled = macos::dmg::bundle_project(&settings, &bundles)?;
|
let bundled = macos::dmg::bundle_project(settings, &bundles)?;
|
||||||
if !bundled.app.is_empty() {
|
if !bundled.app.is_empty() {
|
||||||
bundles.push(Bundle {
|
bundles.push(Bundle {
|
||||||
package_type: PackageType::MacOsBundle,
|
package_type: PackageType::MacOsBundle,
|
||||||
@ -122,33 +121,15 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
PackageType::WindowsMsi => windows::msi::bundle_project(&settings, false)?,
|
PackageType::WindowsMsi => windows::msi::bundle_project(settings, false)?,
|
||||||
PackageType::Nsis => windows::nsis::bundle_project(&settings, false)?,
|
PackageType::Nsis => windows::nsis::bundle_project(settings, false)?,
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
PackageType::Deb => linux::debian::bundle_project(&settings)?,
|
PackageType::Deb => linux::debian::bundle_project(settings)?,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
PackageType::Rpm => linux::rpm::bundle_project(&settings)?,
|
PackageType::Rpm => linux::rpm::bundle_project(settings)?,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
PackageType::AppImage => linux::appimage::bundle_project(&settings)?,
|
PackageType::AppImage => linux::appimage::bundle_project(settings)?,
|
||||||
|
|
||||||
// updater is dependent of multiple bundle, we send our bundles to prevent rebuilding
|
|
||||||
PackageType::Updater => {
|
|
||||||
if !package_types.iter().any(|p| {
|
|
||||||
matches!(
|
|
||||||
p,
|
|
||||||
PackageType::AppImage
|
|
||||||
| PackageType::MacOsBundle
|
|
||||||
| PackageType::Dmg
|
|
||||||
| PackageType::Nsis
|
|
||||||
| PackageType::WindowsMsi
|
|
||||||
)
|
|
||||||
}) {
|
|
||||||
log::warn!("The updater bundle target exists but couldn't find any updater-enabled target, so the updater artifacts won't be generated. Please add one of these targets as well: app, appimage, msi, nsis");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
updater_bundle::bundle_project(&settings, &bundles)?
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
log::warn!("ignoring {}", package_type.short_name());
|
log::warn!("ignoring {}", package_type.short_name());
|
||||||
continue;
|
continue;
|
||||||
@ -161,6 +142,33 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(updater) = settings.updater() {
|
||||||
|
if package_types.iter().any(|package_type| {
|
||||||
|
if updater.v1_compatible {
|
||||||
|
matches!(
|
||||||
|
package_type,
|
||||||
|
PackageType::AppImage
|
||||||
|
| PackageType::MacOsBundle
|
||||||
|
| PackageType::Nsis
|
||||||
|
| PackageType::WindowsMsi
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
matches!(package_type, PackageType::MacOsBundle)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
let updater_paths = updater_bundle::bundle_project(settings, &bundles)?;
|
||||||
|
bundles.push(Bundle {
|
||||||
|
package_type: PackageType::Updater,
|
||||||
|
bundle_paths: updater_paths,
|
||||||
|
});
|
||||||
|
} else if updater.v1_compatible {
|
||||||
|
log::warn!("The updater bundle target exists but couldn't find any updater-enabled target, so the updater artifacts won't be generated. Please add one of these targets as well: app, appimage, msi, nsis");
|
||||||
|
}
|
||||||
|
if updater.v1_compatible {
|
||||||
|
log::warn!("Legacy v1 compatible updater is deprecated and will be removed in v3, change bundle > createUpdaterArtifacts to true when your users are updated to the version with v2 updater plugin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
// Clean up .app if only building dmg or updater
|
// Clean up .app if only building dmg or updater
|
||||||
@ -188,12 +196,16 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bundles.is_empty() {
|
if bundles.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!("No bundles were built").into());
|
||||||
|
}
|
||||||
|
|
||||||
let bundles_wo_updater = bundles
|
let bundles_wo_updater = bundles
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|b| b.package_type != PackageType::Updater)
|
.filter(|b| b.package_type != PackageType::Updater)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let pluralised = if bundles_wo_updater.len() == 1 {
|
let finished_bundles = bundles_wo_updater.len();
|
||||||
|
let pluralised = if finished_bundles == 1 {
|
||||||
"bundle"
|
"bundle"
|
||||||
} else {
|
} else {
|
||||||
"bundles"
|
"bundles"
|
||||||
@ -202,20 +214,19 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<Bundle>> {
|
|||||||
let mut printable_paths = String::new();
|
let mut printable_paths = String::new();
|
||||||
for bundle in &bundles {
|
for bundle in &bundles {
|
||||||
for path in &bundle.bundle_paths {
|
for path in &bundle.bundle_paths {
|
||||||
let mut note = "";
|
let note = if bundle.package_type == crate::PackageType::Updater {
|
||||||
if bundle.package_type == crate::PackageType::Updater {
|
" (updater)"
|
||||||
note = " (updater)";
|
} else {
|
||||||
}
|
""
|
||||||
writeln!(printable_paths, " {}{}", display_path(path), note).unwrap();
|
};
|
||||||
|
let path_display = display_path(path);
|
||||||
|
writeln!(printable_paths, " {path_display}{note}").unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(action = "Finished"; "{} {} at:\n{}", bundles_wo_updater.len(), pluralised, printable_paths);
|
log::info!(action = "Finished"; "{finished_bundles} {pluralised} at:\n{printable_paths}");
|
||||||
|
|
||||||
Ok(bundles)
|
Ok(bundles)
|
||||||
} else {
|
|
||||||
Err(anyhow::anyhow!("No bundles were built").into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check to see if there are icons in the settings struct
|
/// Check to see if there are icons in the settings struct
|
||||||
|
@ -50,7 +50,6 @@ impl From<BundleType> for PackageType {
|
|||||||
BundleType::Nsis => Self::Nsis,
|
BundleType::Nsis => Self::Nsis,
|
||||||
BundleType::App => Self::MacOsBundle,
|
BundleType::App => Self::MacOsBundle,
|
||||||
BundleType::Dmg => Self::Dmg,
|
BundleType::Dmg => Self::Dmg,
|
||||||
BundleType::Updater => Self::Updater,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,10 +155,12 @@ pub struct PackageSettings {
|
|||||||
/// The updater settings.
|
/// The updater settings.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct UpdaterSettings {
|
pub struct UpdaterSettings {
|
||||||
|
/// Should generate v1 compatible zipped updater
|
||||||
|
pub v1_compatible: bool,
|
||||||
/// Signature public key.
|
/// Signature public key.
|
||||||
pub pubkey: String,
|
pub pubkey: String,
|
||||||
/// Args to pass to `msiexec.exe` to run the updater on Windows.
|
/// Args to pass to `msiexec.exe` to run the updater on Windows.
|
||||||
pub msiexec_args: Option<&'static [&'static str]>,
|
pub msiexec_args: &'static [&'static str],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Linux debian bundle settings.
|
/// The Linux debian bundle settings.
|
||||||
@ -865,7 +866,7 @@ impl Settings {
|
|||||||
.unwrap_or(std::env::consts::OS)
|
.unwrap_or(std::env::consts::OS)
|
||||||
.replace("darwin", "macos");
|
.replace("darwin", "macos");
|
||||||
|
|
||||||
let mut platform_types = match target_os.as_str() {
|
let platform_types = match target_os.as_str() {
|
||||||
"macos" => vec![PackageType::MacOsBundle, PackageType::Dmg],
|
"macos" => vec![PackageType::MacOsBundle, PackageType::Dmg],
|
||||||
"ios" => vec![PackageType::IosBundle],
|
"ios" => vec![PackageType::IosBundle],
|
||||||
"linux" => vec![PackageType::Deb, PackageType::Rpm, PackageType::AppImage],
|
"linux" => vec![PackageType::Deb, PackageType::Rpm, PackageType::AppImage],
|
||||||
@ -877,10 +878,6 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.is_update_enabled() {
|
|
||||||
platform_types.push(PackageType::Updater);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(package_types) = &self.package_types {
|
if let Some(package_types) = &self.package_types {
|
||||||
let mut types = vec![];
|
let mut types = vec![];
|
||||||
for package_type in package_types {
|
for package_type in package_types {
|
||||||
@ -1087,14 +1084,4 @@ impl Settings {
|
|||||||
pub fn updater(&self) -> Option<&UpdaterSettings> {
|
pub fn updater(&self) -> Option<&UpdaterSettings> {
|
||||||
self.bundle_settings.updater.as_ref()
|
self.bundle_settings.updater.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is update enabled
|
|
||||||
pub fn is_update_enabled(&self) -> bool {
|
|
||||||
self
|
|
||||||
.bundle_settings
|
|
||||||
.updater
|
|
||||||
.as_ref()
|
|
||||||
.map(|u| !u.pubkey.is_empty())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -646,7 +646,7 @@ pub fn build_wix_app_installer(
|
|||||||
to_json(
|
to_json(
|
||||||
settings
|
settings
|
||||||
.updater()
|
.updater()
|
||||||
.and_then(|updater| updater.msiexec_args)
|
.map(|updater| updater.msiexec_args)
|
||||||
.map(|args| args.join(" "))
|
.map(|args| args.join(" "))
|
||||||
.unwrap_or_else(|| "/passive".to_string()),
|
.unwrap_or_else(|| "/passive".to_string()),
|
||||||
),
|
),
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
"android": {
|
"android": {
|
||||||
"minSdkVersion": 24
|
"minSdkVersion": 24
|
||||||
},
|
},
|
||||||
|
"createUpdaterArtifacts": false,
|
||||||
"iOS": {},
|
"iOS": {},
|
||||||
"icon": [],
|
"icon": [],
|
||||||
"linux": {
|
"linux": {
|
||||||
@ -1519,7 +1520,7 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"targets": {
|
"targets": {
|
||||||
"description": "The bundle targets, currently supports [\"deb\", \"rpm\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\", \"updater\"] or \"all\".",
|
"description": "The bundle targets, currently supports [\"deb\", \"rpm\", \"appimage\", \"nsis\", \"msi\", \"app\", \"dmg\"] or \"all\".",
|
||||||
"default": "all",
|
"default": "all",
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
@ -1527,6 +1528,15 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"createUpdaterArtifacts": {
|
||||||
|
"description": "Produce updaters and their signatures or not",
|
||||||
|
"default": false,
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/Updater"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"publisher": {
|
"publisher": {
|
||||||
"description": "The application's publisher. Defaults to the second element in the identifier string.\n Currently maps to the Manufacturer property of the Windows Installer.",
|
"description": "The application's publisher. Defaults to the second element in the identifier string.\n Currently maps to the Manufacturer property of the Windows Installer.",
|
||||||
"type": [
|
"type": [
|
||||||
@ -1794,12 +1804,34 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"dmg"
|
"dmg"
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Updater": {
|
||||||
|
"description": "Updater type",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"description": "Generates lagacy zipped v1 compatible updaters",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/V1Compatible"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "The Tauri updater bundle.",
|
"description": "Produce updaters and their signatures or not",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"V1Compatible": {
|
||||||
|
"description": "Generates lagacy zipped v1 compatible updaters",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "Generates lagacy zipped v1 compatible updaters",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"updater"
|
"v1Compatible"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -198,41 +198,57 @@ pub fn bundle<A: AppSettings>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bundles = tauri_bundler::bundle_project(settings)
|
let bundles = tauri_bundler::bundle_project(&settings)
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
tauri_bundler::Error::BundlerError(e) => e,
|
tauri_bundler::Error::BundlerError(e) => e,
|
||||||
e => anyhow::anyhow!("{e:#}"),
|
e => anyhow::anyhow!("{e:#}"),
|
||||||
})
|
})
|
||||||
.with_context(|| "failed to bundle project")?;
|
.with_context(|| "failed to bundle project")?;
|
||||||
|
|
||||||
|
sign_updaters(settings, bundles, ci)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_updaters(
|
||||||
|
settings: tauri_bundler::Settings,
|
||||||
|
bundles: Vec<tauri_bundler::Bundle>,
|
||||||
|
ci: bool,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let Some(update_settings) = settings.updater() else {
|
||||||
|
// Updater not enabled
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
let update_enabled_bundles: Vec<&tauri_bundler::Bundle> = bundles
|
let update_enabled_bundles: Vec<&tauri_bundler::Bundle> = bundles
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|bundle| {
|
.filter(|bundle| {
|
||||||
|
if update_settings.v1_compatible {
|
||||||
|
matches!(bundle.package_type, PackageType::Updater)
|
||||||
|
} else {
|
||||||
matches!(
|
matches!(
|
||||||
bundle.package_type,
|
bundle.package_type,
|
||||||
PackageType::Updater | PackageType::Nsis | PackageType::WindowsMsi | PackageType::AppImage
|
PackageType::Updater
|
||||||
|
| PackageType::Nsis
|
||||||
|
| PackageType::WindowsMsi
|
||||||
|
| PackageType::AppImage
|
||||||
)
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Skip if no updater is active
|
if update_enabled_bundles.is_empty() {
|
||||||
if !update_enabled_bundles.is_empty() {
|
return Ok(());
|
||||||
let updater_pub_key = config
|
}
|
||||||
.plugins
|
|
||||||
.0
|
|
||||||
.get("updater")
|
|
||||||
.and_then(|k| k.get("pubkey"))
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.map(|v| v.to_string());
|
|
||||||
|
|
||||||
if let Some(pubkey) = updater_pub_key {
|
|
||||||
// get the public key
|
// get the public key
|
||||||
|
let pubkey = &update_settings.pubkey;
|
||||||
// check if pubkey points to a file...
|
// check if pubkey points to a file...
|
||||||
let maybe_path = Path::new(&pubkey);
|
let maybe_path = Path::new(pubkey);
|
||||||
let pubkey = if maybe_path.exists() {
|
let pubkey = if maybe_path.exists() {
|
||||||
std::fs::read_to_string(maybe_path)?
|
std::fs::read_to_string(maybe_path)?
|
||||||
} else {
|
} else {
|
||||||
pubkey
|
pubkey.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// if no password provided we use an empty string
|
// if no password provided we use an empty string
|
||||||
@ -241,8 +257,8 @@ pub fn bundle<A: AppSettings>(
|
|||||||
.or_else(|| if ci { Some("".into()) } else { None });
|
.or_else(|| if ci { Some("".into()) } else { None });
|
||||||
|
|
||||||
// get the private key
|
// get the private key
|
||||||
let secret_key = match std::env::var("TAURI_SIGNING_PRIVATE_KEY") {
|
let private_key = std::env::var("TAURI_SIGNING_PRIVATE_KEY")
|
||||||
Ok(private_key) => {
|
.map_err(|_| anyhow::anyhow!("A public key has been found, but no private key. Make sure to set `TAURI_SIGNING_PRIVATE_KEY` environment variable."))?;
|
||||||
// check if private_key points to a file...
|
// check if private_key points to a file...
|
||||||
let maybe_path = Path::new(&private_key);
|
let maybe_path = Path::new(&private_key);
|
||||||
let private_key = if maybe_path.exists() {
|
let private_key = if maybe_path.exists() {
|
||||||
@ -250,33 +266,27 @@ pub fn bundle<A: AppSettings>(
|
|||||||
} else {
|
} else {
|
||||||
private_key
|
private_key
|
||||||
};
|
};
|
||||||
updater_signature::secret_key(private_key, password)
|
let secret_key = updater_signature::secret_key(private_key, password)?;
|
||||||
}
|
|
||||||
_ => Err(anyhow::anyhow!("A public key has been found, but no private key. Make sure to set `TAURI_SIGNING_PRIVATE_KEY` environment variable.")),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let pubkey = base64::engine::general_purpose::STANDARD.decode(pubkey)?;
|
let pubkey = base64::engine::general_purpose::STANDARD.decode(pubkey)?;
|
||||||
let pub_key_decoded = String::from_utf8_lossy(&pubkey);
|
let pub_key_decoded = String::from_utf8_lossy(&pubkey);
|
||||||
let public_key = minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?;
|
let public_key = minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?;
|
||||||
|
|
||||||
// make sure we have our package built
|
|
||||||
let mut signed_paths = Vec::new();
|
let mut signed_paths = Vec::new();
|
||||||
for bundle in update_enabled_bundles {
|
for bundle in update_enabled_bundles {
|
||||||
// we expect to have only one path in the vec but we iter if we add
|
// we expect to have only one path in the vec but we iter if we add
|
||||||
// another type of updater package who require multiple file signature
|
// another type of updater package who require multiple file signature
|
||||||
for path in bundle.bundle_paths.iter() {
|
for path in &bundle.bundle_paths {
|
||||||
// sign our path from environment variables
|
// sign our path from environment variables
|
||||||
let (signature_path, signature) = updater_signature::sign_file(&secret_key, path)?;
|
let (signature_path, signature) = updater_signature::sign_file(&secret_key, path)?;
|
||||||
if signature.keynum() != public_key.keynum() {
|
if signature.keynum() != public_key.keynum() {
|
||||||
log::warn!("The updater secret key from `TAURI_PRIVATE_KEY` does not match the public key from `plugins > updater > pubkey`. If you are not rotating keys, this means your configuration is wrong and won't be accepted at runtime when performing update.");
|
log::warn!("The updater secret key from `TAURI_SIGNING_PRIVATE_KEY` does not match the public key from `plugins > updater > pubkey`. If you are not rotating keys, this means your configuration is wrong and won't be accepted at runtime when performing update.");
|
||||||
}
|
}
|
||||||
signed_paths.push(signature_path);
|
signed_paths.push(signature_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print_signed_updater_archive(&signed_paths)?;
|
print_signed_updater_archive(&signed_paths)?;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -284,7 +294,8 @@ pub fn bundle<A: AppSettings>(
|
|||||||
fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> {
|
fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
if !output_paths.is_empty() {
|
if !output_paths.is_empty() {
|
||||||
let pluralised = if output_paths.len() == 1 {
|
let finished_bundles = output_paths.len();
|
||||||
|
let pluralised = if finished_bundles == 1 {
|
||||||
"updater signature"
|
"updater signature"
|
||||||
} else {
|
} else {
|
||||||
"updater signatures"
|
"updater signatures"
|
||||||
@ -297,7 +308,7 @@ fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> {
|
|||||||
tauri_utils::display_path(path)
|
tauri_utils::display_path(path)
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
log::info!( action = "Finished"; "{} {} at:\n{}", output_paths.len(), pluralised, printable_paths);
|
log::info!( action = "Finished"; "{finished_bundles} {pluralised} at:\n{printable_paths}");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ use tauri_bundler::{
|
|||||||
AppCategory, AppImageSettings, BundleBinary, BundleSettings, DebianSettings, DmgSettings,
|
AppCategory, AppImageSettings, BundleBinary, BundleSettings, DebianSettings, DmgSettings,
|
||||||
MacOsSettings, PackageSettings, Position, RpmSettings, Size, UpdaterSettings, WindowsSettings,
|
MacOsSettings, PackageSettings, Position, RpmSettings, Size, UpdaterSettings, WindowsSettings,
|
||||||
};
|
};
|
||||||
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol};
|
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol, Updater};
|
||||||
|
|
||||||
use super::{AppSettings, DevProcess, ExitReason, Interface};
|
use super::{AppSettings, DevProcess, ExitReason, Interface};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -807,11 +807,23 @@ impl AppSettings for RustAppSettings {
|
|||||||
let arch64bits =
|
let arch64bits =
|
||||||
self.target_triple.starts_with("x86_64") || self.target_triple.starts_with("aarch64");
|
self.target_triple.starts_with("x86_64") || self.target_triple.starts_with("aarch64");
|
||||||
|
|
||||||
let updater_settings = if let Some(updater_plugin_config) = config.plugins.0.get("updater") {
|
let updater_enabled = config.bundle.create_updater_artifacts != Updater::Bool(false);
|
||||||
let updater: UpdaterConfig = serde_json::from_value(updater_plugin_config.clone())?;
|
let v1_compatible = matches!(config.bundle.create_updater_artifacts, Updater::String(_));
|
||||||
|
let updater_settings = if updater_enabled {
|
||||||
|
let updater: UpdaterConfig = serde_json::from_value(
|
||||||
|
config
|
||||||
|
.plugins
|
||||||
|
.0
|
||||||
|
.get("updater")
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!("failed to get updater configuration: plugins > updater doesn't exist")
|
||||||
|
})?
|
||||||
|
.clone(),
|
||||||
|
)?;
|
||||||
Some(UpdaterSettings {
|
Some(UpdaterSettings {
|
||||||
|
v1_compatible,
|
||||||
pubkey: updater.pubkey,
|
pubkey: updater.pubkey,
|
||||||
msiexec_args: Some(updater.windows.install_mode.msiexec_args()),
|
msiexec_args: updater.windows.install_mode.msiexec_args(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -224,6 +224,36 @@ fn process_bundle(config: &mut Map<String, Value>) {
|
|||||||
if let Some(license_file) = license_file {
|
if let Some(license_file) = license_file {
|
||||||
bundle_config.insert("licenseFile".into(), license_file);
|
bundle_config.insert("licenseFile".into(), license_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate updater from targets to update field
|
||||||
|
if let Some(targets) = bundle_config.get_mut("targets") {
|
||||||
|
let shuold_migrate = if let Some(targets) = targets.as_array_mut() {
|
||||||
|
// targets: ["updater", ...]
|
||||||
|
if let Some(index) = targets
|
||||||
|
.iter()
|
||||||
|
.position(|target| *target == serde_json::Value::String("updater".to_owned()))
|
||||||
|
{
|
||||||
|
targets.remove(index);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else if let Some(target) = targets.as_str() {
|
||||||
|
// targets: "updater"
|
||||||
|
// targets: "all"
|
||||||
|
if target == "updater" {
|
||||||
|
bundle_config.remove("targets");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
target == "all"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if shuold_migrate {
|
||||||
|
bundle_config.insert("createUpdaterArtifacts".to_owned(), "v1Compatible".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.insert("bundle".into(), bundle_config);
|
config.insert("bundle".into(), bundle_config);
|
||||||
@ -773,6 +803,48 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn migrate_updater_target() {
|
||||||
|
let original = serde_json::json!({
|
||||||
|
"tauri": {
|
||||||
|
"bundle": {
|
||||||
|
"targets": ["nsis", "updater"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let migrated = migrate(&original);
|
||||||
|
assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible");
|
||||||
|
assert_eq!(
|
||||||
|
migrated["bundle"]["targets"].as_array(),
|
||||||
|
Some(&vec!["nsis".into()])
|
||||||
|
);
|
||||||
|
|
||||||
|
let original = serde_json::json!({
|
||||||
|
"tauri": {
|
||||||
|
"bundle": {
|
||||||
|
"targets": "all"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let migrated = migrate(&original);
|
||||||
|
assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible");
|
||||||
|
assert_eq!(migrated["bundle"]["targets"], "all");
|
||||||
|
|
||||||
|
let original = serde_json::json!({
|
||||||
|
"tauri": {
|
||||||
|
"bundle": {
|
||||||
|
"targets": "updater"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let migrated = migrate(&original);
|
||||||
|
assert_eq!(migrated["bundle"]["createUpdaterArtifacts"], "v1Compatible");
|
||||||
|
assert_eq!(migrated["bundle"].get("targets"), None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn migrate_csp_object() {
|
fn migrate_csp_object() {
|
||||||
let original = serde_json::json!({
|
let original = serde_json::json!({
|
||||||
|
Loading…
Reference in New Issue
Block a user