@@ -748,7 +748,7 @@ impl Update {
748748 }
749749}
750750
751- /// Linux (AppImage)
751+ /// Linux (AppImage and Deb )
752752#[ cfg( any(
753753 target_os = "linux" ,
754754 target_os = "dragonfly" ,
@@ -760,12 +760,19 @@ impl Update {
760760 /// ### Expected structure:
761761 /// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler
762762 /// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage
763+ /// ├── [AppName]_[version]_amd64.deb # Debian package
763764 /// └── ...
764765 ///
765- /// We should have an AppImage already installed to be able to copy and install
766- /// the extract_path is the current AppImage path
767- /// tmp_dir is where our new AppImage is found
768766 fn install_inner ( & self , bytes : & [ u8 ] ) -> Result < ( ) > {
767+ if self . is_deb_package ( ) {
768+ self . install_deb ( bytes)
769+ } else {
770+ // Handle AppImage or other formats
771+ self . install_appimage ( bytes)
772+ }
773+ }
774+
775+ fn install_appimage ( & self , bytes : & [ u8 ] ) -> Result < ( ) > {
769776 use std:: os:: unix:: fs:: { MetadataExt , PermissionsExt } ;
770777 let extract_path_metadata = self . extract_path . metadata ( ) ?;
771778
@@ -835,6 +842,162 @@ impl Update {
835842
836843 Err ( Error :: TempDirNotOnSameMountPoint )
837844 }
845+
846+ fn is_deb_package ( & self ) -> bool {
847+ // First check if we're in a typical Debian installation path
848+ let in_system_path = self
849+ . extract_path
850+ . to_str ( )
851+ . map ( |p| p. starts_with ( "/usr" ) )
852+ . unwrap_or ( false ) ;
853+
854+ if !in_system_path {
855+ return false ;
856+ }
857+
858+ // Then verify it's actually a Debian-based system by checking for dpkg
859+ let dpkg_exists = std:: path:: Path :: new ( "/var/lib/dpkg" ) . exists ( ) ;
860+ let apt_exists = std:: path:: Path :: new ( "/etc/apt" ) . exists ( ) ;
861+
862+ // Additional check for the package in dpkg database
863+ let package_in_dpkg = if let Ok ( output) = std:: process:: Command :: new ( "dpkg" )
864+ . args ( [ "-S" , & self . extract_path . to_string_lossy ( ) ] )
865+ . output ( )
866+ {
867+ output. status . success ( )
868+ } else {
869+ false
870+ } ;
871+
872+ // Consider it a deb package only if:
873+ // 1. We're in a system path AND
874+ // 2. We have Debian package management tools AND
875+ // 3. The binary is tracked by dpkg
876+ dpkg_exists && apt_exists && package_in_dpkg
877+ }
878+
879+ fn install_deb ( & self , bytes : & [ u8 ] ) -> Result < ( ) > {
880+ // First verify the bytes are actually a .deb package
881+ if !infer:: archive:: is_deb ( bytes) {
882+ return Err ( Error :: InvalidUpdaterFormat ) ;
883+ }
884+
885+ // Try different temp directories
886+ let tmp_dir_locations = vec ! [
887+ Box :: new( || Some ( std:: env:: temp_dir( ) ) ) as Box <dyn FnOnce ( ) -> Option <PathBuf >>,
888+ Box :: new( dirs:: cache_dir) ,
889+ Box :: new( || Some ( self . extract_path. parent( ) . unwrap( ) . to_path_buf( ) ) ) ,
890+ ] ;
891+
892+ // Try writing to multiple temp locations until one succeeds
893+ for tmp_dir_location in tmp_dir_locations {
894+ if let Some ( path) = tmp_dir_location ( ) {
895+ if let Ok ( tmp_dir) = tempfile:: Builder :: new ( )
896+ . prefix ( "tauri_deb_update" )
897+ . tempdir_in ( path)
898+ {
899+ let deb_path = tmp_dir. path ( ) . join ( "package.deb" ) ;
900+
901+ // Try writing the .deb file
902+ if std:: fs:: write ( & deb_path, bytes) . is_ok ( ) {
903+ // If write succeeds, proceed with installation
904+ return self . try_install_with_privileges ( & deb_path) ;
905+ }
906+ // If write fails, continue to next temp location
907+ }
908+ }
909+ }
910+
911+ // If we get here, all temp locations failed
912+ Err ( Error :: TempDirNotFound )
913+ }
914+
915+ fn try_install_with_privileges ( & self , deb_path : & Path ) -> Result < ( ) > {
916+ // 1. First try using pkexec (graphical sudo prompt)
917+ if let Ok ( status) = std:: process:: Command :: new ( "pkexec" )
918+ . arg ( "dpkg" )
919+ . arg ( "-i" )
920+ . arg ( deb_path)
921+ . status ( )
922+ {
923+ if status. success ( ) {
924+ return Ok ( ( ) ) ;
925+ }
926+ }
927+
928+ // 2. Try zenity or kdialog for a graphical sudo experience
929+ if let Ok ( password) = self . get_password_graphically ( ) {
930+ if self . install_with_sudo ( deb_path, & password) ? {
931+ return Ok ( ( ) ) ;
932+ }
933+ }
934+
935+ // 3. Final fallback: terminal sudo
936+ let status = std:: process:: Command :: new ( "sudo" )
937+ . arg ( "dpkg" )
938+ . arg ( "-i" )
939+ . arg ( deb_path)
940+ . status ( ) ?;
941+
942+ if status. success ( ) {
943+ Ok ( ( ) )
944+ } else {
945+ Err ( Error :: DebInstallFailed )
946+ }
947+ }
948+
949+ fn get_password_graphically ( & self ) -> Result < String > {
950+ // Try zenity first
951+ let zenity_result = std:: process:: Command :: new ( "zenity" )
952+ . args ( [
953+ "--password" ,
954+ "--title=Authentication Required" ,
955+ "--text=Enter your password to install the update:" ,
956+ ] )
957+ . output ( ) ;
958+
959+ if let Ok ( output) = zenity_result {
960+ if output. status . success ( ) {
961+ return Ok ( String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ) ;
962+ }
963+ }
964+
965+ // Fall back to kdialog if zenity fails or isn't available
966+ let kdialog_result = std:: process:: Command :: new ( "kdialog" )
967+ . args ( [ "--password" , "Enter your password to install the update:" ] )
968+ . output ( ) ;
969+
970+ if let Ok ( output) = kdialog_result {
971+ if output. status . success ( ) {
972+ return Ok ( String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ) ;
973+ }
974+ }
975+
976+ Err ( Error :: AuthenticationFailed )
977+ }
978+
979+ fn install_with_sudo ( & self , deb_path : & Path , password : & str ) -> Result < bool > {
980+ use std:: io:: Write ;
981+ use std:: process:: { Command , Stdio } ;
982+
983+ let mut child = Command :: new ( "sudo" )
984+ . arg ( "-S" ) // read password from stdin
985+ . arg ( "dpkg" )
986+ . arg ( "-i" )
987+ . arg ( deb_path)
988+ . stdin ( Stdio :: piped ( ) )
989+ . stdout ( Stdio :: piped ( ) )
990+ . stderr ( Stdio :: piped ( ) )
991+ . spawn ( ) ?;
992+
993+ if let Some ( mut stdin) = child. stdin . take ( ) {
994+ // Write password to stdin
995+ writeln ! ( stdin, "{}" , password) ?;
996+ }
997+
998+ let status = child. wait ( ) ?;
999+ Ok ( status. success ( ) )
1000+ }
8381001}
8391002
8401003/// MacOS
0 commit comments