@@ -748,7 +748,7 @@ impl Update {
748
748
}
749
749
}
750
750
751
- /// Linux (AppImage)
751
+ /// Linux (AppImage and Deb )
752
752
#[ cfg( any(
753
753
target_os = "linux" ,
754
754
target_os = "dragonfly" ,
@@ -760,12 +760,19 @@ impl Update {
760
760
/// ### Expected structure:
761
761
/// ├── [AppName]_[version]_amd64.AppImage.tar.gz # GZ generated by tauri-bundler
762
762
/// │ └──[AppName]_[version]_amd64.AppImage # Application AppImage
763
+ /// ├── [AppName]_[version]_amd64.deb # Debian package
763
764
/// └── ...
764
765
///
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
768
766
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 < ( ) > {
769
776
use std:: os:: unix:: fs:: { MetadataExt , PermissionsExt } ;
770
777
let extract_path_metadata = self . extract_path . metadata ( ) ?;
771
778
@@ -835,6 +842,162 @@ impl Update {
835
842
836
843
Err ( Error :: TempDirNotOnSameMountPoint )
837
844
}
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
+ }
838
1001
}
839
1002
840
1003
/// MacOS
0 commit comments