Include .psd loader
[geeqie.git] / web / geeqie-install-debian.sh
1 #!/bin/bash
2 version="2019-08-10"
3 description=$'
4 Geeqie is an image viewer.
5 This script will download, compile, and install Geeqie on Debian-based systems.
6 If run from a folder that already contains the Geeqie sources, the source
7 code will be updated from the repository.
8 Dialogs allow the user to install additional features, including
9 additional pixbuf loaders.
10
11 Command line options are:
12 -v --version The version of this file
13 -h --help Output this text
14 -c --commit=ID Checkout and compile commit ID
15 -t --tag=TAG Checkout and compile TAG (e.g. v1.4 or v1.3)
16 -b --back=N Checkout commit -N (e.g. "-b 1" for last-but-one commit)
17 -d --debug=yes Compile with debug output
18 '
19
20 # Essential for compiling
21 essential_array=(
22 "git"
23 "build-essential"
24 "autoconf"
25 "libglib2.0-0"
26 "intltool"
27 "libtool"
28 )
29
30 # Optional for both GTK2 and GTK3
31 optional_array=(
32 "LCMS (for color management)"
33 "liblcms2-dev"
34 "exiv2 (for exif handling)"
35 "libgexiv2-dev"
36 "lua (for --remote commands)"
37 "liblua5.1-0-dev"
38 "libffmpegthumbnailer (for mpeg thumbnails)"
39 "libffmpegthumbnailer-dev"
40 "libtiff (for tiff support)"
41 "libtiff-dev"
42 "libjpeg (for jpeg support)"
43 "libjpeg-dev"
44 "librsvg2 (for viewing .svg images)"
45 "librsvg2-common"
46 "libwmf (for viewing .wmf images)"
47 "libwmf0.2-7-gtk"
48 "exiftran (for image rotation)"
49 "exiftran"
50 "imagemagick (for image rotation)"
51 "imagemagick"
52 "ufraw (for RAW file handling)"
53 "ufraw"
54 "exiv2 command line (for jpeg export)"
55 "exiv2"
56 "jpgicc (for jpeg export color correction)"
57 "liblcms2-utils"
58 "markdown (for generating README help file)"
59 "markdown"
60 "gphoto2 (for tethered photography and camera download plugins)"
61 "gphoto2"
62 "libimage-exiftool-perl (for jpeg extraction plugin)"
63 "libimage-exiftool-perl"
64 "libheif (for HEIF support)"
65 "libheif-dev"
66 "libwebp (for WebP images)"
67 "libwebp-dev"
68 "libdjvulibre (for DjVu images)"
69 "libdjvulibre-dev"
70 )
71
72 # Optional for GTK3 only
73 optional_gtk3_array=(
74 "libchamplain gtk (for GPS maps)"
75 "libchamplain-gtk-0.12-dev"
76 "libchamplain (for GPS maps)"
77 "libchamplain-0.12-dev"
78 "libpoppler (for pdf file preview)"
79 "libpoppler-glib-dev"
80 )
81
82 # Optional pixbuf loaders
83 optional_loaders_array=(
84 ".xcf Gimp files"
85 "xcf"
86 )
87
88 ####################################################################
89 # Get System Info
90 # Derived from: https://github.com/coto/server-easy-install (GPL)
91 ####################################################################
92 lowercase()
93 {
94         echo "$1" | sed "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/"
95 }
96
97 systemProfile()
98 {
99         OS=`lowercase \`uname\``
100         KERNEL=`uname -r`
101         MACH=`uname -m`
102
103         if [ "${OS}" == "windowsnt" ]
104         then
105                 OS=windows
106         elif [ "${OS}" == "darwin" ]
107         then
108                 OS=mac
109         else
110                 OS=`uname`
111                 if [ "${OS}" = "SunOS" ]
112                 then
113                         OS=Solaris
114                         ARCH=`uname -p`
115                         OSSTR="${OS} ${REV}(${ARCH} `uname -v`)"
116                 elif [ "${OS}" = "AIX" ]
117                 then
118                         OSSTR="${OS} `oslevel` (`oslevel -r`)"
119                 elif [ "${OS}" = "Linux" ]
120                 then
121                         if [ -f /etc/redhat-release ]
122                         then
123                                 DistroBasedOn='RedHat'
124                                 DIST=`cat /etc/redhat-release |sed s/\ release.*//`
125                                 PSUEDONAME=`cat /etc/redhat-release | sed s/.*\(// | sed s/\)//`
126                                 REV=`cat /etc/redhat-release | sed s/.*release\ // | sed s/\ .*//`
127                         elif [ -f /etc/SuSE-release ]
128                         then
129                                 DistroBasedOn='SuSe'
130                                 PSUEDONAME=`cat /etc/SuSE-release | tr "\n" ' '| sed s/VERSION.*//`
131                                 REV=`cat /etc/SuSE-release | tr "\n" ' ' | sed s/.*=\ //`
132                         elif [ -f /etc/mandrake-release ]
133                         then
134                                 DistroBasedOn='Mandrake'
135                                 PSUEDONAME=`cat /etc/mandrake-release | sed s/.*\(// | sed s/\)//`
136                                 REV=`cat /etc/mandrake-release | sed s/.*release\ // | sed s/\ .*//`
137                         elif [ -f /etc/debian_version ]
138                         then
139                                 DistroBasedOn='Debian'
140                                 if [ -f /etc/lsb-release ]
141                                 then
142                                         DIST=`cat /etc/lsb-release | grep '^DISTRIB_ID' | awk -F=  '{ print $2 }'`
143                                         PSUEDONAME=`cat /etc/lsb-release | grep '^DISTRIB_CODENAME' | awk -F=  '{ print $2 }'`
144                                         REV=`cat /etc/lsb-release | grep '^DISTRIB_RELEASE' | awk -F=  '{ print $2 }'`
145                                 fi
146                         fi
147                         if [ -f /etc/UnitedLinux-release ]
148                         then
149                                 DIST="${DIST}[`cat /etc/UnitedLinux-release | tr "\n" ' ' | sed s/VERSION.*//`]"
150                         fi
151                         OS=`lowercase $OS`
152                         DistroBasedOn=`lowercase $DistroBasedOn`
153                         readonly OS
154                         readonly DIST
155                         readonly DistroBasedOn
156                         readonly PSUEDONAME
157                         readonly REV
158                         readonly KERNEL
159                         readonly MACH
160                 fi
161         fi
162 }
163
164 install_essential()
165 {
166         arraylength=${#essential_array[@]}
167         for (( i=0; i<${arraylength}; i=i+1 ));
168         do
169                 package_query ${essential_array[$i]}
170                 if [ $? != 0 ]
171                 then
172                         package_install ${essential_array[$i]}
173                 fi
174         done
175
176         if [[ $1 == "GTK3" ]]
177         then
178                 package_query "libgtk-3-dev"
179                 if [ $? != 0 ]
180                 then
181                         package_install libgtk-3-dev
182                 fi
183         else
184                 package_query "libgtk2.0-dev"
185                 if [ $? != 0 ]
186                 then
187                         package_install libgtk2.0-dev
188                 fi
189         fi
190 }
191
192 install_options()
193 {
194         if [ -n "$options" ]
195         then
196                 OLDIFS=$IFS
197                 IFS='|'
198                 set $options
199                 while [ $# -gt 0 ];
200                 do
201                         package_install $1
202                         shift
203                 done
204                 IFS=$OLDIFS
205         fi
206 }
207
208 install_xcf()
209 {
210         rm -rf xcf-pixbuf-loader
211         package_install libbz2-dev
212         git clone https://github.com/StephaneDelcroix/xcf-pixbuf-loader.git
213         cd xcf-pixbuf-loader
214         ./autogen.sh
215         make
216
217         # There must be a better way...
218         loader_locn=$(gdk-pixbuf-query-loaders | grep "LoaderDir" | tr -d '#[:space:]')
219
220         OLDIFS=$IFS
221         IFS='='
222         set $loader_locn
223         OLDIFS=$IFS
224
225         if [ -d $2 ]
226         then
227                 sudo --askpass cp .libs/libioxcf.so $2
228                 sudo --askpass gdk-pixbuf-query-loaders --update-cache
229         fi
230         cd -
231         rm -rf xcf-pixbuf-loader
232 }
233
234 install_extra_loaders()
235 {
236         if [ -n "$extra_loaders" ]
237         then
238                 OLDIFS=$IFS
239                 IFS='|'
240                 set $extra_loaders
241                 while [ $# -gt 0 ];
242                 do
243                         case $1 in
244                         "xcf" )
245                                 install_xcf
246                         ;;
247                         esac
248
249                         shift
250                 done
251                 IFS=$OLDIFS
252         fi
253         return
254 }
255
256 uninstall()
257 {
258         current_dir=$(basename $PWD)
259         if [[ $current_dir == "geeqie" ]]
260         then
261                 sudo --askpass make uninstall
262                 zenity --title="Uninstall Geeqie" --width=370 --text="WARNING.\nThis will delete folder:\n\n$PWD\n\nand all sub-folders!" --question --ok-label="Cancel" --cancel-label="OK" 2>/dev/null
263
264                 if [[ $? == 1 ]]
265                 then
266                         cd ..
267                         sudo --askpass rm -rf geeqie
268                 fi
269         else
270                 zenity --title="Uninstall Geeqie" --width=370 --text="This is not a geeqie installation folder!\n\n$PWD" --warning 2>/dev/null
271         fi
272
273         exit_install
274 }
275
276 package_query()
277 {
278         if [[ $DistroBasedOn == "debian" ]]
279         then
280                 res=$(dpkg-query --show --showformat='${Status}' $1 2>>$install_log)
281                 if [[ "$res" == "install ok installed"* ]]
282                 then
283                         status=0
284                 else
285                         status=1
286                 fi
287         fi
288         return $status
289 }
290
291 package_install()
292 {
293         if [[ $DistroBasedOn == "debian" ]]
294         then
295                 sudo --askpass apt-get --assume-yes install $@ >>$install_log 2>&1
296         fi
297 }
298
299 exit_install()
300 {
301         rm $install_pass_script >/dev/null 2>&1
302         #~ rm $install_log >/dev/null 2>&1
303
304         if [[ -p $zen_pipe ]]
305         then
306                 echo "100" > $zen_pipe
307                 echo "#End" > $zen_pipe
308         fi
309
310         zenity --title="$title" --width=370 --text=$'Geeqie is not installed\nLog file: '$install_log --info 2>/dev/null
311
312         rm $zen_pipe >/dev/null 2>&1
313
314         exit 1
315 }
316
317 # Entry point
318
319 # If uninstall has been run, maybe the current directory no longer exists
320 ls $PWD >/dev/null
321 if [[ $? != 0 ]]
322 then
323         zenity --error --title="Install Geeqie and dependencies" --width=370 --text="Folder $PWD does not exist!" 2>/dev/null
324
325         exit
326 fi
327
328 # Check system type
329 systemProfile
330 if [[ $DistroBasedOn != "debian" ]]
331 then
332         zenity --error --title="Install Geeqie and dependencies" --width=370 --text="Unknown operating system:\n
333 Operating System: $OS
334 Distribution: $DIST
335 Psuedoname: $PSUEDONAME
336 Revision: $REV
337 DistroBasedOn: $DistroBasedOn
338 Kernel: $KERNEL
339 Machine: $MACH" 2>/dev/null
340
341         exit
342 fi
343
344 # Parse the comand line
345 OPTS=$(getopt -o vhc:t:b:d: --long version,help,commit:,tag:,back:,debug: -- "$@")
346 eval set -- "$OPTS"
347
348 while true;
349 do
350         case "$1" in
351         -v | --version )
352                 echo "$version"
353                 exit
354                 ;;
355         -h | --help )
356                 echo "$description"
357                 exit
358                 ;;
359         -c | --commit )
360                 COMMIT="$2"
361                 shift
362                 shift
363                 ;;
364         -t | --tag )
365                 TAG="$2"
366                 shift;
367                 shift
368                 ;;
369         -b | --back )
370                 BACK="$2"
371                 shift;
372                 shift
373                 ;;
374         -d | --debug )
375                 DEBUG="$2"
376                 shift;
377                 shift
378                 ;;
379         * ) break
380                 ;;
381         esac
382 done
383
384 # If a Geeqie folder already exists here, warn the user
385 if [ -d "geeqie" ]
386 then
387         zenity --info --title="Install Geeqie and dependencies" --width=370 --text="This script is for use on Ubuntu and other\nDebian-based installations.\nIt will download, compile, and install Geeqie source\ncode and its dependencies.\n\nA sub-folder named \"geeqie\" will be created in the\nfolder this script is run from, and the source code\nwill be downloaded to that sub-folder.\n\nA sub-folder of that name already exists.\nPlease try another folder." 2>/dev/null
388
389         exit
390 fi
391
392 # If it looks like a Geeqie download folder, assume an update
393 if [ -d ".git" ] && [ -d "src" ] && [ -f "geeqie.1" ]
394 then
395         mode="update"
396 else
397         # If it looks like something else is already installed here, warn the user
398         if [ -d ".git" ] || [ -d "src" ]
399         then
400                 zenity --info --title="Install Geeqie and dependencies" --width=370 --text="This script is for use on Ubuntu and other\nDebian-based installations.\nIt will download, compile, and install Geeqie source\ncode and its dependencies.\n\nIt looks like you are running this script from a folder which already has software installed.\n\nPlease try another folder." 2>/dev/null
401
402                 exit
403         else
404                 mode="install"
405         fi
406 fi
407
408 # Use GTK3 as default
409 gk2_installed=FALSE
410 gtk3_installed=TRUE
411
412 if [[ $mode == "install" ]]
413 then
414         message="This script is for use on Ubuntu and other\nDebian-based installations.\nIt will download, compile, and install Geeqie source\ncode and its dependencies.\n\nA sub-folder named \"geeqie\" will be created in the\nfolder this script is run from, and the source code\nwill be downloaded to that sub-folder.\n\nIn this dialog you must select whether to compile\nfor GTK2 or GTK3.\nIf you want to use GPS maps or pdf preview,\nyou must choose GTK3.\nThe GTK2 version has a slightly different\nlook-and-feel compared to the GTK3 version,\nbut otherwise has the same features.\nYou may easily switch between the two after\ninstallation.\n\nIn subsequent dialogs you may choose which\noptional features to install."
415
416         title="Install Geeqie and dependencies"
417         install_option=TRUE
418 else
419         message="This script is for use on Ubuntu and other\nDebian-based installations.\nIt will update the Geeqie source code and its\ndependencies, and will compile and install Geeqie.\n\nYou may also switch the installed version from\nGTK2 to GTK3 and vice versa.\n\nIn this dialog you must select whether to compile\nfor GTK2 or GTK3.\nIf you want to use GPS maps or pdf preview,\nyou must choose GTK3.\nThe GTK2 version has a slightly different\nlook-and-feel compared to the GTK3 version,\nbut otherwise has the same features.\n\nIn subsequent dialogs you may choose which\noptional features to install."
420
421         title="Update Geeqie and re-install"
422         install_option=FALSE
423
424         # When updating, use previous installation as default
425         if [[ -f config.log ]]
426         then
427                 grep gtk-2.0 config.log >/dev/null
428                 if [[ $? != 0 ]]
429                 then
430                         gtk2_installed=FALSE
431                         gtk3_installed=TRUE
432                 else
433                         gtk2_installed=TRUE
434                         gtk3_installed=FALSE
435                 fi
436         fi
437 fi
438
439 # Ask whether to install GTK2 or GTK3 or uninstall
440
441 gtk_version=$(zenity --title="$title" --width=370 --text="$message" --list --radiolist --column "" --column "" "$gtk3_installed" "GTK3 (required for GPS maps and pdf preview)" "$gtk2_installed" "GTK2" FALSE "Uninstall" --cancel-label="Cancel" --ok-label="OK" --hide-header 2>/dev/null)
442
443 if [[ $? == 1 ]]
444 then
445         exit
446 fi
447
448 # Environment variable SUDO_ASKPASS cannot be "zenity --password",
449 # so create a temporary script containing the command
450 install_pass_script=$(mktemp --tmpdir geeqie_install_XXXXXX.sh)
451 echo $'#!/bin/bash
452 zenity --password --title=\"'$title$'\" --width=370 2>/dev/null
453 if [[ $? > 0 ]]
454 then
455         exit 1
456 fi
457 \n' > $install_pass_script
458 chmod +x $install_pass_script
459 export SUDO_ASKPASS=$install_pass_script
460
461 if [[ $gtk_version == "Uninstall" ]]
462 then
463         uninstall
464         exit
465 fi
466
467 # Put the install log in tmp, to avoid writing to PWD during a new install
468 rm install.log 2>/dev/null
469 install_log=$(mktemp --tmpdir geeqie_install_XXXXXX.log)
470
471 sleep 100 | zenity --title="$title" --text="Checking for installed files" --width=370 --progress --pulsate 2>/dev/null &
472 zen_pid=$!
473
474 # Get the standard options that are not yet installed
475 arraylength=${#optional_array[@]}
476 for (( i=0; i<${arraylength}; i=i+2 ));
477 do
478         package_query ${optional_array[$i+1]}
479         if [ $? != 0 ]
480         then
481                 if [ -z "$option_string" ]
482                 then
483                         option_string="$install_option"$'\n'"${optional_array[$i]}"$'\n'"${optional_array[$i+1]}"
484                 else
485                         option_string="$option_string"$'\n'"$install_option"$'\n'"${optional_array[$i]}"$'\n'"${optional_array[$i+1]}"
486                 fi
487         fi
488 done
489
490 # If GTK3 required, get the GTK3 options not yet installed
491 if [[ "$gtk_version" == "GTK3"* ]]
492 then
493         arraylength=${#optional_gtk3_array[@]}
494         for (( i=0; i<${arraylength}; i=i+2 ));
495         do
496                 package_query ${optional_gtk3_array[$i+1]}
497                 if [ $? != 0 ]
498                 then
499                         if [ -z "$option_string" ]
500                         then
501                                 option_string="$install_option"$'\n'"${optional_gtk3_array[$i]}"$'\n'"${optional_gtk3_array[$i+1]}"
502                         else
503                                 option_string="$option_string"$'\n'"$install_option"$'\n'"${optional_gtk3_array[$i]}"$'\n'"${optional_gtk3_array[$i+1]}"
504                         fi
505                 fi
506         done
507 fi
508
509 # Get the optional loaders not yet installed
510 ((i=0))
511 gdk-pixbuf-query-loaders | grep xcf >/dev/null
512 if [[ $? == 1 ]]
513 then
514         if [ -z "$loaders_string" ]
515         then
516                 loaders_string=$'FALSE\n'"${optional_loaders_array[$i]}"$'\n'"${optional_loaders_array[$i+1]}"
517         else
518                 loaders_string="$loaders_string"$'\nFALSE\n'"${optional_loaders_array[$i]}"$'\n'"${optional_loaders_array[$i+1]}"
519         fi
520 fi
521
522 kill $zen_pid 2>/dev/null
523
524 # Ask the user which options to install
525 if [ -n "$option_string" ]
526 then
527         options=$(echo "$option_string" | zenity --title="$title" --width=400 --height=500 --list --checklist --text 'Select which library files to install:' --column='Select' --column='Library files' --column='Library' --hide-column=3 --print-column=3 2>/dev/null)
528
529         if [[ $? == 1 ]]
530         then
531                 exit_install
532         fi
533 fi
534
535 # Ask the user which extra loaders to install
536 if [ -n "$loaders_string" ]
537 then
538         extra_loaders=$(echo "$loaders_string" | zenity --title="$title" --width=370 --height=400 --list --checklist --text 'These loaders are not part of the main repository,\nbut are known to work to some extent.' --column='Select' --column='Library files' --column='Library' --hide-column=3 --print-column=3 2>/dev/null)
539
540         if [[ $? == 1 ]]
541         then
542                 exit_install
543         fi
544 fi
545
546 # Start of Zenity progress section
547 zen_pipe=$(mktemp --dry-run --tmpdir geeqie_install_pipe_XXXXXX)
548 mkfifo $zen_pipe
549 (tail  -f $zen_pipe 2>/dev/null) | zenity --progress --title="$title" --width=370 --text="Installing options..." --auto-close --auto-kill --percentage=0 2>/dev/null &
550
551 echo "2" > $zen_pipe
552 echo "#Installing essential libraries..." > $zen_pipe
553
554 install_essential $gtk_version
555
556 echo "4" > $zen_pipe
557 echo "#Installing options..." > $zen_pipe
558
559 install_options
560
561 echo "6" > $zen_pipe
562 echo "#Installing extra loaders..." > $zen_pipe
563
564 install_extra_loaders
565
566 echo "10" > $zen_pipe
567 echo "#Getting new sources from server..." > $zen_pipe
568
569 if [[ $mode == "install" ]]
570 then
571         ret=$(git clone git://www.geeqie.org/geeqie.git >>$install_log 2>&1)
572 else
573         git checkout master >>$install_log 2>&1
574         if [[ $? != 0 ]]
575         then
576                 git_error=$(tail -n5 $install_log 2>&1)
577                 zenity --title="$title" --width=370 --height=400 --error --text=$'Git error:\n\n'"$git_error" 2>/dev/null
578                 exit_install
579         fi
580         ret=$(git pull >>$install_log 2>&1)
581 fi
582
583 if [[ $? != 0 ]]
584 then
585         git_error=$(tail -n5 $install_log 2>&1)
586         zenity --title="$title" --width=370 --height=400 --error --text=$'Git error:\n\n'"$git_error" 2>/dev/null
587         exit_install
588 fi
589
590 echo "20" > $zen_pipe
591 echo "#Cleaning installed version..." > $zen_pipe
592
593 if [[ $mode == "install" ]]
594 then
595         cd geeqie
596 else
597         sudo --askpass make uninstall >>$install_log 2>&1
598         sudo --askpass make maintainer-clean >>$install_log 2>&1
599 fi
600
601 echo "30" > $zen_pipe
602 echo "#Checkout required version..." > $zen_pipe
603
604 if [[ "$BACK" ]]
605 then
606         ret=$(git checkout master~"$BACK" >>$install_log 2>&1)
607         if [[ $? != 0 ]]
608         then
609                 git_error=$(tail -n5 $install_log 2>&1)
610                 zenity --title="$title" --width=370 --height=400 --error --text=$'Git error:\n\n'"$git_error" 2>/dev/null
611                 exit_install
612         fi
613 elif [[ "$COMMIT" ]]
614 then
615         ret=$(git checkout "$COMMIT" >>$install_log 2>&1)
616         if [[ $? != 0 ]]
617         then
618                 git_error=$(tail -n5 $install_log 2>&1)
619                 zenity --title="$title" --width=370 --height=400 --error --text=$'Git error:\n\n'"$git_error" 2>/dev/null
620                 exit_install
621         fi
622 elif [[ "$TAG" ]]
623 then
624         ret=$(git checkout "$TAG" >>$install_log 2>&1)
625         if [[ $? != 0 ]]
626         then
627         echo "error"
628                 git_error=$(tail -n5 $install_log 2>&1)
629                 zenity --title="$title" --width=370 --height=400 --error --text=$'Git error:\n\n'"$git_error" 2>/dev/null
630                 exit_install
631                 exit
632         fi
633 fi
634 if [[ "$DEBUG" == "yes" ]]
635 then
636         debug_opt=""
637 else
638         debug_opt="--disable-debug-log"
639 fi
640
641 echo "40" > $zen_pipe
642 echo "#Creating configuration files..." > $zen_pipe
643
644 if [[ $gtk_version == "GTK3"* ]]
645 then
646         ./autogen.sh "$debug_opt" >>$install_log 2>&1
647 else
648         ./autogen.sh "$debug_opt" --disable-gtk3 >>$install_log 2>&1
649 fi
650
651 echo "60" > $zen_pipe
652 echo "#Compiling..." > $zen_pipe
653
654 export CFLAGS=$CFLAGS" -Wno-deprecated-declarations"
655 export CXXFLAGS=$CXXFLAGS" -Wno-deprecated-declarations"
656 make -j >>$install_log 2>&1
657
658 if [[ $? != 0 ]]
659 then
660         zenity --title="$title" --width=370 --height=400 --error --text=$'Compile error' 2>/dev/null
661         exit_install
662         exit
663 fi
664
665 echo "90 " > $zen_pipe
666 echo "#Installing Geeqie..." > $zen_pipe
667
668 sudo --askpass make install >>$install_log 2>&1
669
670 rm $install_pass_script
671 mv -f $install_log install.log;
672
673 echo "100 " > $zen_pipe
674 rm $zen_pipe
675
676 (for i in $(seq 0 4 100); do echo "$i"; sleep 0.1; done) | zenity --progress --title="$title" --width=370 --text="Geeqie installation complete...\n$zonedetect_message" --auto-close --percentage=0 2>/dev/null
677
678 exit