Files
pristontale-eu-linux/install.sh

360 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SOURCE_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_VERSION="$(sed -n 's/^__version__ = "\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)"$/\1/p' "${SOURCE_ROOT}/ptlinux/__init__.py")"
VERSION="${PTLINUX_VERSION:-$SOURCE_VERSION}"
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
then
printf 'Invalid launcher version: %s (expected X.Y.Z)\n' "$VERSION" >&2
exit 2
fi
if [[ "$VERSION" != "$SOURCE_VERSION" ]]
then
printf 'Launcher version %s does not match source version %s.\n' "$VERSION" "$SOURCE_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}/ptdiag/logs.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}/ptlinux/native/pt-identity-probe.exe" \
|| ! -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}/ptlinux/native" \
"${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}/ptlinux/native/pt-identity-probe.exe" \
"${STAGING_RELEASE}/ptlinux/native/pt-identity-probe.exe"
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"
install -m 0644 "${SOURCE_ROOT}/ptdiag/logs.py" "${STAGING_RELEASE}/ptdiag/logs.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 -B -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' "Launcher files installed."
DOCTOR_READY=1
if (( RUN_DOCTOR == 1 ))
then
if ! "${BIN_HOME}/pristontale-eu" doctor
then
DOCTOR_READY=0
fi
else
printf '%s\n' "Host readiness check skipped."
fi
printf '%s\n' "Start PristonTale EU with:"
printf ' %q gui\n' "${BIN_HOME}/pristontale-eu"
case ":${PATH:-}:" in
*":${BIN_HOME}:"*)
;;
*)
printf '%s\n' \
"Note: ${BIN_HOME} is not in PATH for this shell." \
"The application-menu shortcut already uses the full path."
if [[ "${SHELL##*/}" == "fish" ]]
then
printf '%s\n' "Enable the short command in Fish with:"
printf ' fish_add_path %q\n' "$BIN_HOME"
else
printf '%s\n' "Enable the short command in this Bash/Zsh session with:"
printf ' export PATH=%q:"$PATH"\n' "$BIN_HOME"
fi
;;
esac
if (( DOCTOR_READY == 0 ))
then
printf '%s\n' \
"Launcher files were installed, but this host is not ready to run the game." \
"Resolve the diagnostics above, then run the launcher command again." >&2
exit 1
fi
printf '%s\n' "Installation ready."