Files
UltraGrid/data/scripts/Linux-AppImage/AppRun
Martin Pulec 3efd4186a7 AppRun: exec UG binary with exec
This respawns the process instead of the script. The advantage is that
when the AppImage process is killed, UG is killed as well, eg. in this
case:
```
UltraGrid-continuous-x86_64.AppImage -t testcard & (sleep 3; kill $!)
```

Otherwise just the parent process (the shell) is killed, leaving the child
(UG) running on background, which is undesirable. See also:
<https://stackoverflow.com/questions/8533377/why-child-process-still-alive-after-parent-process-was-killed-in-linux>

For the above command, `kill -SIGTERM -$!` would be required to do
the job, but the parent shell process keeps "running" in the background
and `bg` needs to be run.
2024-02-19 15:10:38 +01:00

347 lines
14 KiB
Bash
Executable File

#!/bin/sh -eu
# shellcheck disable=SC2059 # use of $bold et al. in printf format str
# defaults
use_bundled_libva_default=0
# if not run from AppImage (eg. extracted), use default values
APPIMAGE=${APPIMAGE-none}
ARGV0=${ARGV0-$0}
bold=$(tput bold || true)
red=$(tput setaf 1 || true)
reset=$(tput sgr0 || true)
DIR=$(dirname "$0")
readonly AI_LIB_PATH="$DIR"/usr/lib
n_ld_library_path="$AI_LIB_PATH"${LD_LIBRARY_PATH:+":$LD_LIBRARY_PATH"}
LD_PRELOAD=${LD_PRELOAD-}
# there is an issue with running_from_path() which evaluates this executable
# as being system-installed
#export PATH=$DIR/usr/bin:$PATH
export QT_QPA_FONTDIR="$DIR/usr/share/fonts"
export UG_FONT_DIR="$DIR/usr/share/fonts"
QT_PLUGIN_PATH=$(set -- "$DIR"/usr/lib/qt?/plugins; echo "$1")
export QT_PLUGIN_PATH
export QT_QPA_PLATFORM_PLUGIN_PATH="$QT_PLUGIN_PATH/platforms"
get_tools() {(
find "$DIR/usr/bin" -mindepth 1 -exec basename {} \; | tr '\n' ' '
)}
usage() {
printf "usage:\n"
printf "\t${bold}${red}%s${reset} ${bold}[--gui [args]]${reset}\n" "$ARGV0"
printf "\t\tinvokes GUI\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}--appimage-help${reset}\n" "$ARGV0"
printf "\t\tprints AppImage related options\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-h | --help | --fullhelp${reset}\n" "$ARGV0"
printf "\t\tprints this help (extended version with ${bold}--fullhelp${reset})\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-m|--man [uv|hd-rum-transcode]${reset}\n" "$ARGV0"
printf "\t\tprints manual page\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-u|--update [args]${reset}\n" "$ARGV0"
printf "\t\tupdates AppImage (args will be passed to appimageupdatetool)\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-o|--tool uv --help${reset}\n" "$ARGV0"
printf "\t\tprints command-line UltraGrid help\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-o|--tool <t> [args]${reset}\n" "$ARGV0"
printf "\t\tinvokes specified tool\n"
printf "\t\ttool may be one of: ${bold}%s${reset}\n" "$(get_tools)"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}args${reset}\n" "$ARGV0"
printf "\t\tinvokes command-line UltraGrid\n"
printf "\n"
}
usage_aux() {
printf "environment variables:\n"
printf "\tULTRAGRID_AUTOUPDATE: autoupdate interval in days (0 - check always); -1 - disable update advice\n"
printf "\tULTRAGRID_BUNDLED_LIBVA: 1 - use bundled libva; 0 - use system libva (if available), default %d\n" $use_bundled_libva_default
printf "\tULTRAGRID_USE_FIREJAIL: run the UltraGrid executable with firejail. If\n"
printf "\t the variable contains a profile path (ends with\n"
printf "\t '.profile'), it will be used.\n"
printf "\tFIREJAIL_DEBUG: print used Firejail options\n"
printf "\tFIREJAIL_OPTS: custom options to be passed to Firejail\n"
printf "\n"
}
update_check_days=90
## Trigger update if $ULTRAGRID_AUTOUPDATE days passed.
## If $ULTRAGRID_AUTOUPDATE unset or empty, print update hint if UG binary is older than $update_check_days days.
handle_updates() {(
if [ "${ULTRAGRID_AUTOUPDATE-0}" -eq -1 ]; then
return
fi
if [ "$APPIMAGE" = none ]; then
return
fi
if expr "$APPIMAGE" : '.*continuous' > /dev/null; then
update_check_days=30
fi
if [ "${ULTRAGRID_AUTOUPDATE-}" ] && [ "$ULTRAGRID_AUTOUPDATE" -ge 0 ]; then
update_check_days=$ULTRAGRID_AUTOUPDATE
fi
appimage_mtime=$(stat -c %Y "$APPIMAGE")
curr_timestamp=$(date +%s)
if [ "$curr_timestamp" -lt $((appimage_mtime + update_check_days * 24 * 60 * 60)) ]; then
return
fi
if [ "${ULTRAGRID_AUTOUPDATE-}" ] && [ "$ULTRAGRID_AUTOUPDATE" -ge 0 ]; then
if ! "$DIR/appimageupdatetool" -j "$APPIMAGE" && "$DIR/appimageupdatetool" "$APPIMAGE"; then
ULTRAGRID_AUTOUPDATE=-1 # prevent update loops if the new version not stored for some reason
exec "$APPIMAGE" "$@" # launch the updated version
fi
touch "$APPIMAGE" # update AppImage mtime otherwise if there are no updates after elapsing the interval, the check would run always
return
fi
printf "UltraGrid binary older than %d days, consider checking updates:\n" "$update_check_days"
printf "\n"
printf "%s -u\n" "$ARGV0"
printf "\t- updates AppImage\n"
printf "%s -u -j; [ \$? -eq 1 ] && echo Update available || echo No update available\n" "$ARGV0"
printf "\t- check for update without actually updating\n"
printf "%s -u -h\n" "$ARGV0"
printf "\t- prints update options\n"
printf "\n"
printf "Hint: you can set environment variable ULTRAGRID_AUTOUPDATE to 1 for daily automatic update or -1 to suppress the above message.\n"
printf "\n"
)}
## Tries to find system libva. If found it is preloaded (+libva-drm+libva-x11)
## @retval 0 if preloaded; 1 otherwise
set_libva_ld_preload() {
if [ ! -f "$AI_LIB_PATH/ultragrid/ultragrid_vcompress_libavcodec.so" ]; then
return 0
fi
LOADER=$(get_loader)
if [ ! -x "$LOADER" ]; then
return 1
fi
AI_LIBAVCODEC_LIB=$(LD_LIBRARY_PATH=$n_ld_library_path \
LD_TRACE_LOADED_OBJECTS=1 "$LOADER" \
"$AI_LIB_PATH/ultragrid/ultragrid_vcompress_libavcodec.so" |
grep libavcodec.so | grep -v 'not found' | awk '{print $3}')
if [ -z "$AI_LIBAVCODEC_LIB" ]; then
return 1
fi
LIBVA_LIB=$(LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$AI_LIBAVCODEC_LIB" | grep libva.so | grep -v 'not found' | awk '{print $3}')
if [ -z "$LIBVA_LIB" ]; then
return 1
fi
libva_libs=$LIBVA_LIB
# add also libva-drm, libva-x11 if present
for n in libva-drm libva-x11; do
NAME=$(echo "$LIBVA_LIB" | sed s/libva/$n/)
if [ -f "$NAME" ]; then
libva_libs=$libva_libs:$NAME
fi
done
# preload also VDPAU
libvdpau_lib=$(LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$AI_LIBAVCODEC_LIB"\
| grep libvdpau.so | grep -v 'not found' | awk '{print $3}')
if [ -n "$libvdpau_lib" ]; then
libva_libs=$libva_libs:$libvdpau_lib
fi
export LD_PRELOAD="$libva_libs${LD_PRELOAD:+:$LD_PRELOAD}"
}
## Tries to set LD_PRELOAD to system libva (see set_libva_ld_preload()). If failed, sets path to bundled libva drivers.
setup_vaapi() {
ULTRAGRID_BUNDLED_LIBVA=${ULTRAGRID_BUNDLED_LIBVA:-$use_bundled_libva_default}
if [ "$ULTRAGRID_BUNDLED_LIBVA" -ne 1 ]; then
if ! set_libva_ld_preload; then
echo "${bold}${red}Could not set system libva, using bundled libraries instead!${reset}" >&2
ULTRAGRID_BUNDLED_LIBVA=1
fi
fi
if [ "$ULTRAGRID_BUNDLED_LIBVA" -eq 1 ]; then
if [ -d "$DIR/usr/lib/va" ] && [ -z "${LIBVA_DRIVERS_PATH:-}" ]; then
export LIBVA_DRIVERS_PATH="$AI_LIB_PATH/va"
fi
fi
}
setup_wayland() {
# use bundled Wayland libs only when not running on Wayland, otherwise system ones
if [ -z "${WAYLAND_DISPLAY-}" ]; then
n_ld_library_path="$n_ld_library_path:$AI_LIB_PATH/wayland"
fi
}
add_whitelist() {
if expr "$1" : /tmp >/dev/null; then
printf -- "$separator--read-write=\"%s\" --mkdir=\"%s\"" "$1" "$1"
else
printf -- "$separator--whitelist=\"%s\"" "$1"
fi
separator=' '
}
# realpath + expand tilde expansion (like ~/video.mp4; only single ~ now)
expand_path() {
# shellcheck disable=SC2088 # intentional
if ! expr "$1" : '~/' >/dev/null; then
realpath "$1"
else
realpath "$HOME${1#\~}"
fi
}
whitelist_file_path() {
abs_path=$(expand_path "$1")
add_whitelist "$abs_path"
if expr "$n" : 'dump:' >/dev/null || expr "$n" : '--record' >/dev/null
then
printf -- "$separator--mkdir=\"%s\"" "$abs_path" # may not exist
fi
if expr "$n" : '.*file[:=]' >/dev/null || expr "$n" : '.*filename' \
>/dev/null; then
parent_dir=$(dirname "$abs_path")
if [ "$parent_dir" = /dev ]; then
return
fi
# whitelist the parent folder to allow file creation
printf -- "$separator--whitelist=\"%s\"" "$parent_dir"
fi
}
## Parse params to get custom rules like whitelists that may be needed to add
## @todo spaces inside paths doesn't work
get_custom_firejail_rules() {
separator=''
playback_path=$(echo "$@" | sed -n 's/.*--playback \([^ :]*\).*/\1/p')
if [ -n "$playback_path" ]; then
add_whitelist "$(expand_path "$playback_path")"
fi
# print every argument of "filename=", "file[:=]", "dump:" or "--record=" pattern
for n in "$@"; do
file_path=$(echo "$n" | sed -n \
-e 's/.*filename=\([^:]*\).*/\1/p' \
-e 's/.*\(n\|name\)=\([^:]*\).*/\2/p' \
-e 's/.*file[:=]\([^:]*\).*/\1/p' \
-e 's/dump:\([^:]*\).*/\1/p' \
-e 's/^--record=\([^:]*\).*/\1/p')
if [ -n "$file_path" ]; then
whitelist_file_path "$file_path"
fi
done
# -d dump or -d dummy:dump_to_file
if expr "$*" : '.*dump' >/dev/null || expr "$*" : '.*record' >/dev/null; then
add_whitelist "$(pwd)"
fi
if ! expr "$DIR" : /tmp >/dev/null; then # add AppRun path if not in tmp (== extracted AppImage)
add_whitelist "$(expand_path "$DIR")"
fi
if [ -z "$separator" ]; then # no whitelist was set
printf -- "--private"
fi
# screen pw - requires root (could be disabled by default.profile) and dbus
if expr "$*" : '.*screen' >/dev/null; then
printf " --ignore=noroot"
elif firejail --version | grep -iq "d-\{0,1\}bus.*enabled"; then
printf " --dbus-system=none"
fi
if ! expr "$*" : '.*/mnt' >/dev/null &&
! expr "$*" : '.*/media' >/dev/null &&
! expr "$*" : '.*/run/mount' >/dev/null &&
! expr "$*" : '.*/run/media' >/dev/null
then
printf " --disable-mnt"
fi
}
# shellcheck source=/dev/null
. "$DIR/scripts/preload.sh"
setup_vaapi
setup_wayland
# ultragrid_aplay_jack.so is not used because it loads JACK with dlopen,
# while portaudio is linked directly to JACK library
set_ld_preload ultragrid_aplay_portaudio.so libjack
set_ld_preload ultragrid_vidcap_pipewire.so libpipewire
if [ -n "${ULTRAGRID_USE_FIREJAIL-}" ] && [ "$ULTRAGRID_USE_FIREJAIL" != 0 ] && [ "$ULTRAGRID_USE_FIREJAIL" != no ]; then
command -v firejail >/dev/null || { echo "Firejail not present in system!"; exit 1; }
fj_opt_file=$(mktemp)
if expr "$ULTRAGRID_USE_FIREJAIL" : '.*\.profile' >/dev/null; then
FIREJAIL_OPTS="${FIREJAIL_OPTS+$FIREJAIL_OPTS }--profile=$ULTRAGRID_USE_FIREJAIL"
else
FJ_TMPDIR=${TMPDIR-/tmp/ultragrid-$(id -u)}
FIREJAIL_OPTS="${FIREJAIL_OPTS+$FIREJAIL_OPTS }--caps.drop=all\
--ipc-namespace --nonewprivs --protocol=unix,inet,inet6,netlink --seccomp\
--private-bin=bash --private-opt=none --mkdir=$FJ_TMPDIR\
--read-write=$FJ_TMPDIR --writable-var"
FIREJAIL_OPTS="$FIREJAIL_OPTS $(get_custom_firejail_rules "$@") --private-etc=alsa,group,hostname,ld.so.conf,ld.so.cache,ld.so.conf.d,nsswitch.conf,passwd,resolv.conf --ignore=novideo"
if ! expr "$FIREJAIL_OPTS" : '.*--read-write=/tmp ' > /dev/null; then
FIREJAIL_OPTS="--read-only=/tmp $FIREJAIL_OPTS"
fi
fi
if firejail --help | grep -q -- --keep-var-tmp; then
FIREJAIL_OPTS="$FIREJAIL_OPTS --keep-var-tmp"
fi
echo "firejail --env=LD_PRELOAD=${LD_PRELOAD} --env=LD_LIBRARY_PATH=${n_ld_library_path}${FJ_TMPDIR+ --env=TMPDIR=${FJ_TMPDIR}} $FIREJAIL_OPTS " > "$fj_opt_file"
if [ "${FIREJAIL_DEBUG:-}" ]; then
opts=$(cat "$fj_opt_file")
printf "Firejail arguments:\n\t%s\n\n" "$opts"
fi
fi
run() {
tool=$1
shift
export LD_LIBRARY_PATH="$n_ld_library_path"
if [ -n "${fj_opt_file-}" ] && [ "$tool" != uv-qt ]; then
# shellcheck disable=SC2046 # intentional
eval exec $(cat "$fj_opt_file"; rm "$fj_opt_file") "$DIR/usr/bin/$tool" '"$@"'
else
exec "$DIR/usr/bin/$tool" "$@"
fi
}
if [ $# -eq 0 ] || [ "${1-}" = "--gui" ]; then
handle_updates "$@"
if [ $# -eq 0 ]; then usage; else shift; fi
if [ -x "$DIR/usr/bin/uv-qt" ]; then
run uv-qt --with-uv "$DIR/usr/bin/uv" "$@"
else
echo "GUI was not compiled in!" >&2
exit 1
fi
elif [ "$1" = "-o" ] || [ "$1" = "--tool" ]; then
handle_updates "$@"
TOOL=$2
shift 2
if [ "$TOOL" = help ]; then
printf "available tools: ${bold}%s${reset}\n" "$(get_tools)"
exit 0
fi
# shellcheck disable=SC2086
run "$TOOL" "$@"
elif [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage
exit 0
elif [ "$1" = "--fullhelp" ]; then
usage
usage_aux
exit 0
elif { [ $# -eq 1 ] || [ $# -eq 2 ]; } && { [ "$1" = "-m" ] || [ "$1" = "--man" ]; }; then
PAGE=${2:-uv}
man -l "$DIR/usr/share/man/man1/$PAGE.1"
elif [ "$1" = "-u" ] || [ "$1" = "--update" ]; then
shift
if [ -d "$DIR/appimageupdatetool-lib" ]; then
export LD_LIBRARY_PATH="$DIR/appimageupdatetool-lib"
fi
touch "$APPIMAGE" # update AppImage mtime to avoid update notices if there are no updates avalable but were checked for
"$DIR/appimageupdatetool" ${1+"$@" }"$APPIMAGE"
else
handle_updates "$@"
run uv "$@"
fi