309 lines
8.9 KiB
Bash
Executable File
309 lines
8.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
SOURCE_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
VERSION="${PTLINUX_VERSION:-0.5.1}"
|
|
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
|
then
|
|
printf 'Invalid launcher version: %s (expected X.Y.Z)\n' "$VERSION" >&2
|
|
exit 2
|
|
fi
|
|
DATA_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}"
|
|
BIN_HOME="${HOME}/.local/bin"
|
|
APPLICATION_HOME="${DATA_HOME}/applications"
|
|
PIXMAP_HOME="${DATA_HOME}/pixmaps"
|
|
ICON_PATH="${PIXMAP_HOME}/eu.pristontale.PTLinux.png"
|
|
DEFAULT_INSTALL_DIRECTORY="${HOME}/Games/PristontaleEU"
|
|
LEGACY_INSTALL_DIRECTORY="${DATA_HOME}/pristontale-eu"
|
|
INSTALL_DIRECTORY_EXPLICIT=0
|
|
if [[ -n "${PTLINUX_INSTALL_DIR:-}" ]]
|
|
then
|
|
INSTALL_DIRECTORY="$PTLINUX_INSTALL_DIR"
|
|
INSTALL_DIRECTORY_EXPLICIT=1
|
|
else
|
|
INSTALL_DIRECTORY="$DEFAULT_INSTALL_DIRECTORY"
|
|
fi
|
|
PRESERVED_LEGACY_INSTALL=0
|
|
PYTHON_BIN="${PTLINUX_PYTHON:-python3}"
|
|
DRY_RUN=0
|
|
RUN_DOCTOR=1
|
|
INSTALL_DEPENDENCIES=1
|
|
|
|
usage()
|
|
{
|
|
printf '%s\n' \
|
|
"Usage: ./install.sh [--install-dir PATH] [--dry-run] [--skip-deps] [--skip-doctor]" \
|
|
"" \
|
|
" --install-dir PATH Store the app, managed game, and runtime data at PATH." \
|
|
" Default: ~/Games/PristontaleEU"
|
|
}
|
|
|
|
while (( $# > 0 ))
|
|
do
|
|
case "$1" in
|
|
--install-dir)
|
|
if (( $# < 2 ))
|
|
then
|
|
printf '%s\n' "--install-dir requires a path." >&2
|
|
exit 2
|
|
fi
|
|
INSTALL_DIRECTORY="$2"
|
|
INSTALL_DIRECTORY_EXPLICIT=1
|
|
shift 2
|
|
;;
|
|
--install-dir=*)
|
|
INSTALL_DIRECTORY="${1#*=}"
|
|
INSTALL_DIRECTORY_EXPLICIT=1
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
--skip-doctor)
|
|
RUN_DOCTOR=0
|
|
shift
|
|
;;
|
|
--skip-deps)
|
|
INSTALL_DEPENDENCIES=0
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
printf 'Unknown option: %s\n' "$1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if (( INSTALL_DIRECTORY_EXPLICIT == 0 )) \
|
|
&& [[ ! -d "${DEFAULT_INSTALL_DIRECTORY}/app/ptlinux" \
|
|
&& ! -d "${DEFAULT_INSTALL_DIRECTORY}/app/current/ptlinux" ]] \
|
|
&& [[ -d "${LEGACY_INSTALL_DIRECTORY}/app/ptlinux" \
|
|
|| -d "${LEGACY_INSTALL_DIRECTORY}/app/current/ptlinux" \
|
|
|| -f "${LEGACY_INSTALL_DIRECTORY}/launcher-prefix/drive_c/Program Files (x86)/Pristontale EU/Game.exe" ]]
|
|
then
|
|
INSTALL_DIRECTORY="$LEGACY_INSTALL_DIRECTORY"
|
|
PRESERVED_LEGACY_INSTALL=1
|
|
fi
|
|
|
|
if [[ -z "$INSTALL_DIRECTORY" \
|
|
|| "$INSTALL_DIRECTORY" == *$'\n'* \
|
|
|| "$INSTALL_DIRECTORY" == *$'\r'* ]]
|
|
then
|
|
printf '%s\n' "Install directory is empty or contains invalid characters." >&2
|
|
exit 2
|
|
fi
|
|
|
|
case "$INSTALL_DIRECTORY" in
|
|
"~")
|
|
INSTALL_DIRECTORY="$HOME"
|
|
;;
|
|
"~/"*)
|
|
INSTALL_DIRECTORY="${HOME}/${INSTALL_DIRECTORY:2}"
|
|
;;
|
|
"~"*)
|
|
printf '%s\n' "Use an absolute path instead of another user's ~ directory." >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
if [[ "$INSTALL_DIRECTORY" != /* ]]
|
|
then
|
|
INSTALL_DIRECTORY="${PWD}/${INSTALL_DIRECTORY}"
|
|
fi
|
|
if command -v realpath >/dev/null 2>&1
|
|
then
|
|
INSTALL_DIRECTORY="$(realpath -m -- "$INSTALL_DIRECTORY")"
|
|
fi
|
|
if [[ "$INSTALL_DIRECTORY" == "/" ]]
|
|
then
|
|
printf '%s\n' "Refusing to use the filesystem root as the install directory." >&2
|
|
exit 2
|
|
fi
|
|
APP_ROOT="${INSTALL_DIRECTORY}/app"
|
|
RELEASES_ROOT="${APP_ROOT}/releases"
|
|
RELEASE_ROOT="${RELEASES_ROOT}/${VERSION}"
|
|
|
|
if [[ ! -d "${SOURCE_ROOT}/ptlinux" \
|
|
|| ! -f "${SOURCE_ROOT}/ptdiag/assbin.py" \
|
|
|| ! -f "${SOURCE_ROOT}/ptdiag/__init__.py" \
|
|
|| ! -f "${SOURCE_ROOT}/ptlinux/desktop_entry.py" \
|
|
|| ! -f "${SOURCE_ROOT}/ptlinux/launcher_wrapper.py" \
|
|
|| ! -f "${SOURCE_ROOT}/ptlinux/self_update.py" \
|
|
|| ! -f "${SOURCE_ROOT}/ptlinux/launcher-update-public.pem" \
|
|
|| ! -f "${SOURCE_ROOT}/packaging/eu.pristontale.PTLinux.desktop" \
|
|
|| ! -f "${SOURCE_ROOT}/packaging/eu.pristontale.PTLinux.png" \
|
|
|| ! -f "${SOURCE_ROOT}/packaging/pristontale-eu" \
|
|
|| ! -f "${SOURCE_ROOT}/packaging/install-dependencies.sh" ]]
|
|
then
|
|
printf '%s\n' "Installer source is incomplete." >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf '%s\n' "Installing PristonTale EU Linux launcher for the current user"
|
|
if (( PRESERVED_LEGACY_INSTALL == 1 ))
|
|
then
|
|
printf '%s\n' " Existing installation found; preserving its previous data location."
|
|
fi
|
|
printf ' Install data: %s\n' "$INSTALL_DIRECTORY"
|
|
printf ' Application: %s\n' "$APP_ROOT"
|
|
printf ' Command: %s\n' "${BIN_HOME}/pristontale-eu"
|
|
printf ' Desktop: %s\n' "${APPLICATION_HOME}/eu.pristontale.PTLinux.desktop"
|
|
|
|
if (( INSTALL_DEPENDENCIES == 1 ))
|
|
then
|
|
# shellcheck source=packaging/install-dependencies.sh
|
|
source "${SOURCE_ROOT}/packaging/install-dependencies.sh"
|
|
ptlinux_install_host_dependencies "$DRY_RUN"
|
|
else
|
|
printf '%s\n' "Skipping host dependency installation."
|
|
fi
|
|
|
|
if (( DRY_RUN == 1 ))
|
|
then
|
|
exit 0
|
|
fi
|
|
|
|
if ! command -v "$PYTHON_BIN" >/dev/null 2>&1
|
|
then
|
|
printf 'Python was not found after dependency installation: %s\n' "$PYTHON_BIN" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! "$PYTHON_BIN" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)'
|
|
then
|
|
printf '%s\n' "Python 3.11 or newer is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
install -d -m 0755 "$APP_ROOT" "$RELEASES_ROOT" "$BIN_HOME" "$APPLICATION_HOME" "$PIXMAP_HOME"
|
|
STAGING_RELEASE="${RELEASES_ROOT}/.${VERSION}.$$.staging"
|
|
if [[ -e "$STAGING_RELEASE" || -L "$STAGING_RELEASE" ]]
|
|
then
|
|
printf 'Launcher staging path already exists: %s\n' "$STAGING_RELEASE" >&2
|
|
exit 1
|
|
fi
|
|
install -d -m 0755 "$STAGING_RELEASE"
|
|
install -d -m 0755 "${STAGING_RELEASE}/ptlinux" "${STAGING_RELEASE}/ptdiag"
|
|
install -m 0644 "${SOURCE_ROOT}/ptlinux/"*.py "${STAGING_RELEASE}/ptlinux/"
|
|
install -m 0644 \
|
|
"${SOURCE_ROOT}/ptlinux/launcher-update-public.pem" \
|
|
"${STAGING_RELEASE}/ptlinux/launcher-update-public.pem"
|
|
install -m 0644 "${SOURCE_ROOT}/ptdiag/__init__.py" "${STAGING_RELEASE}/ptdiag/__init__.py"
|
|
install -m 0644 "${SOURCE_ROOT}/ptdiag/assbin.py" "${STAGING_RELEASE}/ptdiag/assbin.py"
|
|
printf '{\n "schema": 1,\n "version": "%s"\n}\n' "$VERSION" > "${STAGING_RELEASE}/release.json"
|
|
chmod 0644 "${STAGING_RELEASE}/release.json"
|
|
|
|
if ! "$PYTHON_BIN" -I -c \
|
|
'import sys; sys.path.insert(0, sys.argv[1]); import ptlinux, ptlinux.cli, ptlinux.self_update; raise SystemExit(0 if ptlinux.__version__ == sys.argv[2] else 1)' \
|
|
"$STAGING_RELEASE" "$VERSION"
|
|
then
|
|
printf '%s\n' "The staged launcher failed its import/version check." >&2
|
|
rm -r -- "$STAGING_RELEASE"
|
|
exit 1
|
|
fi
|
|
|
|
REPLACED_RELEASE=""
|
|
if [[ -e "$RELEASE_ROOT" || -L "$RELEASE_ROOT" ]]
|
|
then
|
|
if [[ ! -d "$RELEASE_ROOT" || -L "$RELEASE_ROOT" ]]
|
|
then
|
|
printf 'Existing launcher release is not a directory: %s\n' "$RELEASE_ROOT" >&2
|
|
rm -r -- "$STAGING_RELEASE"
|
|
exit 1
|
|
fi
|
|
REPLACED_RELEASE="${RELEASES_ROOT}/.${VERSION}.$$.replaced"
|
|
mv -- "$RELEASE_ROOT" "$REPLACED_RELEASE"
|
|
fi
|
|
if ! mv -- "$STAGING_RELEASE" "$RELEASE_ROOT"
|
|
then
|
|
if [[ -n "$REPLACED_RELEASE" && -d "$REPLACED_RELEASE" ]]
|
|
then
|
|
mv -- "$REPLACED_RELEASE" "$RELEASE_ROOT"
|
|
fi
|
|
exit 1
|
|
fi
|
|
if [[ -n "$REPLACED_RELEASE" && -d "$REPLACED_RELEASE" ]]
|
|
then
|
|
rm -r -- "$REPLACED_RELEASE"
|
|
fi
|
|
|
|
CURRENT_TARGET=""
|
|
if [[ -L "${APP_ROOT}/current" ]]
|
|
then
|
|
CURRENT_TARGET="$(readlink -- "${APP_ROOT}/current")"
|
|
if [[ ! "$CURRENT_TARGET" =~ ^releases/[0-9]+\.[0-9]+\.[0-9]+$ ]]
|
|
then
|
|
printf 'Existing launcher current link is unsafe: %s\n' "$CURRENT_TARGET" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -d "${APP_ROOT}/${CURRENT_TARGET}" ]]
|
|
then
|
|
printf 'Existing launcher current link is broken: %s\n' "$CURRENT_TARGET" >&2
|
|
exit 1
|
|
fi
|
|
elif [[ -e "${APP_ROOT}/current" ]]
|
|
then
|
|
printf 'Launcher current path is not a symbolic link: %s\n' "${APP_ROOT}/current" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -L "${APP_ROOT}/previous" ]]
|
|
then
|
|
PREVIOUS_TARGET="$(readlink -- "${APP_ROOT}/previous")"
|
|
if [[ ! "$PREVIOUS_TARGET" =~ ^releases/[0-9]+\.[0-9]+\.[0-9]+$ \
|
|
|| ! -d "${APP_ROOT}/${PREVIOUS_TARGET}" ]]
|
|
then
|
|
printf 'Existing launcher previous link is unsafe or broken: %s\n' "$PREVIOUS_TARGET" >&2
|
|
exit 1
|
|
fi
|
|
elif [[ -e "${APP_ROOT}/previous" ]]
|
|
then
|
|
printf 'Launcher previous path is not a symbolic link: %s\n' "${APP_ROOT}/previous" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n "$CURRENT_TARGET" && "$CURRENT_TARGET" != "releases/${VERSION}" ]]
|
|
then
|
|
PREVIOUS_LINK="${APP_ROOT}/.previous.$$.tmp"
|
|
ln -s -- "$CURRENT_TARGET" "$PREVIOUS_LINK"
|
|
mv -Tf -- "$PREVIOUS_LINK" "${APP_ROOT}/previous"
|
|
fi
|
|
CURRENT_LINK="${APP_ROOT}/.current.$$.tmp"
|
|
ln -s -- "releases/${VERSION}" "$CURRENT_LINK"
|
|
mv -Tf -- "$CURRENT_LINK" "${APP_ROOT}/current"
|
|
|
|
"$PYTHON_BIN" "${RELEASE_ROOT}/ptlinux/launcher_wrapper.py" \
|
|
"${SOURCE_ROOT}/packaging/pristontale-eu" \
|
|
"$APP_ROOT" \
|
|
"$INSTALL_DIRECTORY" \
|
|
"${BIN_HOME}/pristontale-eu"
|
|
install -m 0644 \
|
|
"${SOURCE_ROOT}/packaging/eu.pristontale.PTLinux.png" \
|
|
"$ICON_PATH"
|
|
"$PYTHON_BIN" "${RELEASE_ROOT}/ptlinux/desktop_entry.py" \
|
|
"${SOURCE_ROOT}/packaging/eu.pristontale.PTLinux.desktop" \
|
|
"${BIN_HOME}/pristontale-eu" \
|
|
"$ICON_PATH" \
|
|
"${APPLICATION_HOME}/eu.pristontale.PTLinux.desktop"
|
|
|
|
if command -v desktop-file-validate >/dev/null 2>&1
|
|
then
|
|
desktop-file-validate "${APPLICATION_HOME}/eu.pristontale.PTLinux.desktop"
|
|
fi
|
|
|
|
if command -v update-desktop-database >/dev/null 2>&1
|
|
then
|
|
update-desktop-database "$APPLICATION_HOME" >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
printf '%s\n' "Application installed."
|
|
if (( RUN_DOCTOR == 1 ))
|
|
then
|
|
"${BIN_HOME}/pristontale-eu" doctor || true
|
|
fi
|