#!/bin/bash # # Install TinyPilot from a TinyPilot bundle tarball. # # Examples: # sudo install-bundle /tmp/tinypilot-community-20230103T1619Z-1.8.0-74+7623988.tgz # sudo install-bundle https://example.com/tinypilot-community-20230103T1619Z-1.8.0-74+7623988.tgz # # This script is only for development, though unlike other dev scripts, it # installs on the device. Regular users should use the get-tinypilot.sh or # get-tinypilot-pro.sh scripts. # # get-tinypilot.sh and get-tinypilot-pro.sh do not use this script because they # can't rely on it being available locally at install time. # Exit on unset variable. set -u # Exit on first error. set -e if (( "${EUID}" != 0 )); then echo "This script requires root privileges." >&2 echo "Please re-run with sudo:" >&2 echo " sudo $0 $*" >&2 exit 1 fi print_help() { cat << EOF Usage: ${0##*/} [-h] bundle_path Install the specified TinyPilot bundle on the system. bundle_path: Local filesystem path or URL to TinyPilot bundle. -h Display this help and exit. EOF } # Parse command-line arguments. while getopts 'h' opt; do case "${opt}" in h) print_help exit ;; *) print_help >&2 exit 1 esac done # Ensure 'bundle_path' is given. shift "$((OPTIND - 1))" if (( $# == 0 )); then echo 'Input parameter missing: bundle_path' >&2 exit 1 fi # Echo commands before executing them, by default to stderr. set -x readonly BUNDLE_PATH="$1" # Historically, the TinyPilot bundle was unpacked to the device's disk, where it # persisted. Since then, we've moved to the use of a volatile RAMdisk, which # avoids excessive writes to the filesystem. As a result, this legacy installer # directory has been orphaned and is now removed as part of this script's # `clean_up` function. # https://github.com/tiny-pilot/tinypilot/issues/1357 readonly LEGACY_INSTALLER_DIR='/opt/tinypilot-updater' # The RAMdisk size is broadly based on the combined size of the following: # - The TinyPilot bundle archive # - The unpacked TinyPilot bundle archive, after running the bundle's `install` # script # - About 50 MiB of temporary files # - At least a 20% safety margin # Use the following command to help you estimate a sensible size allocation: # du --summarize --total --bytes "${INSTALLER_DIR}" "${BUNDLE_FILE}" readonly RAMDISK_SIZE_MIB=560 AVAILABLE_MEMORY_MIB="$(free --mebi | grep --fixed-strings 'Mem:' | tr --squeeze-repeats ' ' | cut --delimiter ' ' --fields 7)" readonly AVAILABLE_MEMORY_MIB # Assign a provisional installation directory for our `clean_up` function. INSTALLER_DIR='/mnt/tinypilot-installer' # Remove temporary files & directories. clean_up() { umount --lazy "${INSTALLER_DIR}" || true rm -rf \ "${LEGACY_INSTALLER_DIR}" \ "${INSTALLER_DIR}" } # Always clean up before exiting. trap 'clean_up' EXIT # Determine the installation directory. Use RAMdisk if there is enough memory, # otherwise, fall back to regular disk. if (( "${AVAILABLE_MEMORY_MIB}" >= "${RAMDISK_SIZE_MIB}" )); then # Mount volatile RAMdisk. # Note: `tmpfs` can use swap space when the device's physical memory is under # pressure. Alternatively, we could use `ramfs` which doesn't use swap space, # but also doesn't enforce a filesystem size limit, unlike `tmpfs`. Considering # that our goal is to reduce disk writes and not necessarily eliminate them # altogether, the possibility of using swap space is an acceptable compromise in # exchange for limiting memory usage. # https://github.com/tiny-pilot/tinypilot/issues/1357 sudo mkdir "${INSTALLER_DIR}" sudo mount \ --types tmpfs \ --options "size=${RAMDISK_SIZE_MIB}m" \ --source tmpfs \ --target "${INSTALLER_DIR}" \ --verbose else # Fall back to installing from disk. # HACK: If we let mktemp use the default /tmp directory, the system begins # purging files before the end of the script for some reason. We use /var/tmp # as a workaround. INSTALLER_DIR="$(mktemp \ --tmpdir='/var/tmp' \ --directory)" fi readonly INSTALLER_DIR # Use a temporary directory within the installer directory so that we take # advantage of RAMdisk if we're using one. readonly TMPDIR="${INSTALLER_DIR}/tmp" export TMPDIR mkdir "${TMPDIR}" if [[ "${BUNDLE_PATH}" == http* ]]; then readonly BUNDLE_FILE="${INSTALLER_DIR}/bundle.tgz" curl "${BUNDLE_PATH}" \ --location \ --output "${BUNDLE_FILE}" \ --show-error \ --silent else readonly BUNDLE_FILE="${BUNDLE_PATH}" fi # Extract tarball to installer directory. tar \ --gunzip \ --extract \ --file "${BUNDLE_FILE}" \ --directory "${INSTALLER_DIR}" # Run install. pushd "${INSTALLER_DIR}" ./install