#!/bin/sh # scripts/bootstrap.sh # Toshy Installation Bootstrap Script # https://github.com/RedBearAK/toshy # shellcheck disable=SC2034 VERSION='20260731' # NOTE: This is deliberately the ONLY script in the repo written for POSIX # 'sh' instead of the usual '#!/usr/bin/env bash'. It is the one script that # must run BEFORE Toshy's native package install has had a chance to put # 'bash' (or anything else) on the system. Some distros with busybox userland # (e.g. Alpine) ship no bash at all, only 'ash' as /bin/sh. Every other # script in the repo runs post-install and may assume bash freely. # # Consequences kept in mind throughout this file: # - No '[[ ]]' tests, no process substitution, no arrays. # - No 'read -p' (busybox ash has it, but dash — /bin/sh on Debian/Ubuntu — # does not). Prompts are printed separately with 'printf'. # - busybox 'wget' only understands a short-option subset, so downloads # stick to flags valid for both GNU and busybox wget. # NOTE: deliberately no 'set -e'. Every command that matters is checked # explicitly below. And 'set -e' is silently disabled inside if/&&/||/! # conditions anyway, so it provides partial coverage while looking like # total coverage. # Store the original directory ORIGINAL_DIR="$(pwd)" # Source archives come from codeload, which serves a tarball for ANY ref: a # branch name, a tag, or a commit SHA (full or abbreviated). Fetching an archive # means 'git' is NOT required to install Toshy. That matters, because 'git' is # not preinstalled on many desktop distros, and it is Toshy's own native package # install that puts it there. Requiring it just to bootstrap was backwards. TOSHY_ARCHIVE_BASE="https://codeload.github.com/RedBearAK/toshy/tar.gz" # Default ref (the common case is still a branch name) DEFAULT_BRANCH="main" SUGGESTED_BRANCH="dev_beta" # Function to ensure echoes are visible echo_unbuffered() { echo "$@" >&2 } # Helper for interactive reads (handles piped input). # Usage: read_interactive "Prompt text: " variable_name # The prompt is printed with 'printf' because 'read -p' is not POSIX # (busybox ash has it, but dash does not). read_interactive() { printf '%s' "$1" >&2 if [ -t 0 ]; then read -r "$2" else read -r "$2" /dev/null; then echo_unbuffered echo_unbuffered "ERROR: This looks like WSL (Windows Subsystem for Linux)." echo_unbuffered echo_unbuffered " Toshy needs the kernel's 'evdev' and 'uinput' interfaces, real" echo_unbuffered " input devices, and a desktop session. WSL provides none of" echo_unbuffered " these, so Toshy cannot work here." echo_unbuffered exit 1 fi # Confirm a download tool is present. One of these is almost certainly here # already: the quick-install one-liner uses curl or wget to fetch this script. DOWNLOAD_TOOL="" if command -v curl >/dev/null 2>&1; then DOWNLOAD_TOOL="curl" elif command -v wget >/dev/null 2>&1; then DOWNLOAD_TOOL="wget" else echo_unbuffered "ERROR: Either 'curl' or 'wget' is required to fetch Toshy." echo_unbuffered " Please install one of them and run this script again." exit 1 fi # Confirm 'tar' is present (used to unpack the source archive). if ! command -v tar >/dev/null 2>&1; then echo_unbuffered "ERROR: 'tar' is required to unpack Toshy but was not found." echo_unbuffered " Please install tar and run this script again." exit 1 fi # Create a unique folder name with timestamp FILE_NAME="toshy_$(date +%Y%m%d_%H%M)" DOWNLOAD_DIR="$HOME/Downloads" # Initialize install args INSTALL_ARGS="install" # Set by check_system_updated() only when a FRESH install is confirmed updated, # so setup_toshy.py won't re-ask the same question. Empty on reinstalls. SKIP_UPDATE_CHECK_ARG="" # Set by check_admin_capability(); latched into setup_toshy.py so the admin # question is asked exactly once, here, as the first question of the process. ADMIN_CAPABLE_ARG="" echo_unbuffered echo_unbuffered echo_unbuffered "=== Toshy Bootstrap Installer ===" # STEP 0: Confirm system is updated (fresh installs only), before any other prompts # NixOS cannot use the normal install path (no package manager logic; the # runtime/system layers are managed by the Nix flake), but downloading and # unpacking the source is still useful. Skip the installer questions and, # after unpacking, print Nix-specific guidance instead of launching setup. IS_NIXOS=0 if [ -r /etc/os-release ] && [ "$(. /etc/os-release && echo "${ID:-}")" = "nixos" ]; then IS_NIXOS=1 echo_unbuffered echo_unbuffered "NixOS detected. The source will be downloaded and unpacked, but the" echo_unbuffered "normal installer will not run; Nix-specific steps follow at the end." fi if [ "$IS_NIXOS" != "1" ]; then check_admin_capability check_system_updated # STEP 1: Get install options FIRST get_install_options # Append the update-check latch (empty unless a fresh install was confirmed) INSTALL_ARGS="$INSTALL_ARGS $SKIP_UPDATE_CHECK_ARG $ADMIN_CAPABLE_ARG" fi # STEP 2: Get ref selection echo_unbuffered BRANCH=$(get_branch) # Set up paths. The archive is unpacked into a fixed, known directory, and the # archive's own top-level folder is stripped on extraction. That folder's name # varies by ref type ('toshy-main', 'toshy-Toshy_v26.06.0', 'toshy-34bd0ec...'), # and having to guess it is what drove the earlier move to a git clone. Stripping # it means the requested ref never affects the resulting path, and nothing has to # guess anything. DOWNLOAD_PARENT="$DOWNLOAD_DIR/$FILE_NAME" TOSHY_DIR="$DOWNLOAD_PARENT/toshy" TARBALL_PATH="$DOWNLOAD_PARENT/toshy_source.tar.gz" ARCHIVE_URL="$TOSHY_ARCHIVE_BASE/$BRANCH" echo_unbuffered echo_unbuffered "Starting fetch..." echo_unbuffered "Ref: $BRANCH" if ! mkdir -p "$TOSHY_DIR"; then echo_unbuffered "ERROR: Could not create download folder: $TOSHY_DIR" exit 1 fi echo_unbuffered echo_unbuffered "Downloading Toshy source archive..." echo_unbuffered if ! download_file "$ARCHIVE_URL" "$TARBALL_PATH"; then echo_unbuffered echo_unbuffered "ERROR: Download failed: $ARCHIVE_URL" echo_unbuffered " Verify the branch, tag, or commit exists: $BRANCH" echo_unbuffered " (And check your internet connection.)" exit 1 fi # A truncated download or a served error page will not list as a gzip tarball. if ! tar -tzf "$TARBALL_PATH" >/dev/null 2>&1; then echo_unbuffered "ERROR: Downloaded file is not a valid archive: $TARBALL_PATH" exit 1 fi echo_unbuffered echo_unbuffered "Unpacking archive..." if ! tar -xzf "$TARBALL_PATH" -C "$TOSHY_DIR" --strip-components=1; then echo_unbuffered "ERROR: Could not unpack archive: $TARBALL_PATH" exit 1 fi rm -f "$TARBALL_PATH" # Sanity check: the setup script must exist, or the archive was not what we think. if [ ! -f "$TOSHY_DIR/setup_toshy.py" ]; then echo_unbuffered "ERROR: Setup script not found after unpacking: $TOSHY_DIR/setup_toshy.py" echo_unbuffered " The archive did not contain the expected Toshy source tree." exit 1 fi # GitHub archives preserve the executable bit, but do not rely on it. chmod +x "$TOSHY_DIR/setup_toshy.py" 2>/dev/null echo_unbuffered "Done." # Navigate to the setup directory if ! cd "$TOSHY_DIR"; then echo_unbuffered "ERROR: Could not enter the Toshy folder: $TOSHY_DIR" exit 1 fi # Define the Ctrl+C trap function ctrl_c() { echo_unbuffered echo_unbuffered "Installation paused." echo_unbuffered echo_unbuffered "To navigate to the Toshy directory and run the setup manually, use:" echo_unbuffered " cd \"$TOSHY_DIR\"" echo_unbuffered exit 0 } # Set up the trap before setup launches trap ctrl_c INT echo_unbuffered echo_unbuffered "Download complete. Toshy source unpacked to:" echo_unbuffered " $TOSHY_DIR" if [ "$IS_NIXOS" = "1" ]; then echo_unbuffered echo_unbuffered "NixOS: stopping here (the normal installer does not apply)." echo_unbuffered "Continue with the Nix-specific steps, from the unpacked folder:" echo_unbuffered echo_unbuffered " cd \"$TOSHY_DIR\"" echo_unbuffered echo_unbuffered "First time (no system flake yet): bash ./nix/nixos-scaffold.sh" echo_unbuffered "Runtime already in place: bash ./nix/install-user-files.sh" echo_unbuffered echo_unbuffered "Full details: nix/README.md in that folder." exit 0 fi # Strong visual separation before setup launches echo_unbuffered echo_unbuffered echo_unbuffered "================================================================================" echo_unbuffered "================================================================================" echo_unbuffered "=== ===" echo_unbuffered "=== LAUNCHING TOSHY SETUP SCRIPT ===" echo_unbuffered "=== ===" echo_unbuffered "================================================================================" echo_unbuffered "================================================================================" echo_unbuffered echo_unbuffered # Run the setup script with collected arguments # shellcheck disable=SC2086 if ! ./setup_toshy.py $INSTALL_ARGS; then echo_unbuffered "Setup script execution failed." exit 1 fi echo_unbuffered echo_unbuffered "Toshy bootstrap installation completed successfully!" echo_unbuffered # Return to original directory cd "$ORIGINAL_DIR" || exit 1 # End of file #