#!/usr/bin/env sh # # install.sh - End-user installer for the `move` CLI. # # Curl-pipe ready: # # curl -fsSL https://gitea.cahlen.com/nokeo08/Move/raw/branch/master/install.sh | sh # # What it does: # 1. Detect platform; bail on anything @nut-tree-fork/nut-js doesn't ship. # 2. Require Bun; fail with a clear hint if missing (no auto-install). # 3. Resolve XDG-compliant install paths. # 4. Idempotence check via a version marker file. # 5. Download the source tarball from Gitea, extract under the install dir. # 6. `bun install --production` (skips devDependencies). # 7. Drop a small wrapper script as `move` on the user's bin dir. # 8. Seed the user's config file with defaults, only if one doesn't already # exist at $XDG_CONFIG_HOME/move/config.json. # 9. Verify PATH, surface macOS Accessibility hint, print final status. # # Env vars (all optional): # MOVE_VERSION Branch or tag to install. Default: master. # MOVE_FORCE Set to 1 to reinstall even if the version marker matches. # XDG_DATA_HOME Source install root (default $HOME/.local/share). # Final source location is $XDG_DATA_HOME/move. # XDG_BIN_HOME Wrapper install root (default $HOME/.local/bin). # Final binary location is $XDG_BIN_HOME/move. # XDG_CONFIG_HOME User config root (default $HOME/.config). # Default config file path is $XDG_CONFIG_HOME/move/config.json. # # POSIX sh; no bashisms. set -eu REPO_OWNER="nokeo08" REPO_NAME="Move" GITEA_HOST="gitea.cahlen.com" MOVE_VERSION="${MOVE_VERSION:-master}" MOVE_FORCE="${MOVE_FORCE:-0}" INSTALL_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/move" BIN_DIR="${XDG_BIN_HOME:-$HOME/.local/bin}" CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/move" CONFIG_FILE="$CONFIG_DIR/config.json" die() { printf 'Error: %s\n' "$1" >&2 exit 1 } # Refuse to operate on a directory that points somewhere catastrophic. # The XDG_* env vars are broadly-scoped and the user could set them to # anything; our paths always culminate in `.../move`, but a malformed # XDG var could still resolve to something like '/move' which we don't # want to `rm -rf` or otherwise mass-write into. assert_safe_dir() { case "$1" in '' | '/' | "$HOME" | "$HOME/" | '/move') die "refusing to operate on '$1' (too broad)" ;; esac } # --- Prerequisite tools ------------------------------------------------------ for tool in curl tar mktemp; do if ! command -v "$tool" >/dev/null 2>&1; then die "$tool is required (not found in PATH)" fi done # --- Platform detection ------------------------------------------------------ OS=$(uname -s) ARCH=$(uname -m) case "$OS" in Darwin|Linux) ;; *) die "unsupported OS '$OS' (move supports macOS and Linux)" ;; esac case "$ARCH" in arm64|aarch64|x86_64|amd64) ;; *) die "unsupported architecture '$ARCH'" ;; esac # --- Bun check (hard fail; no auto-install) ---------------------------------- if ! command -v bun >/dev/null 2>&1; then cat >&2 < Using bun %s\n' "$BUN_VERSION" # --- Idempotence check ------------------------------------------------------- assert_safe_dir "$INSTALL_DIR" if [ "$MOVE_FORCE" != "1" ] && [ -f "$INSTALL_DIR/.installed-version" ]; then CURRENT=$(cat "$INSTALL_DIR/.installed-version" 2>/dev/null || printf '') if [ "$CURRENT" = "$MOVE_VERSION" ]; then printf 'move %s is already installed at %s/move.\n' "$MOVE_VERSION" "$BIN_DIR" printf 'Set MOVE_FORCE=1 to reinstall, or set MOVE_VERSION to a different ref.\n' exit 0 fi fi # --- Clean install dir ------------------------------------------------------- mkdir -p "$BIN_DIR" rm -rf "$INSTALL_DIR" mkdir -p "$INSTALL_DIR" # --- Download source --------------------------------------------------------- TARBALL_URL="https://$GITEA_HOST/$REPO_OWNER/$REPO_NAME/archive/$MOVE_VERSION.tar.gz" TARBALL_TMP=$(mktemp) || die "could not create temp file" cleanup() { rm -f "$TARBALL_TMP" } trap cleanup EXIT INT TERM printf '==> Downloading %s\n' "$TARBALL_URL" if ! curl -fsSL "$TARBALL_URL" -o "$TARBALL_TMP"; then die "could not download $TARBALL_URL (check MOVE_VERSION='$MOVE_VERSION' and network)" fi printf '==> Extracting source to %s\n' "$INSTALL_DIR" if ! tar -xzf "$TARBALL_TMP" -C "$INSTALL_DIR" --strip-components=1; then die "could not extract tarball from $TARBALL_URL" fi # --- Install runtime deps ---------------------------------------------------- printf '==> Installing runtime dependencies (bun install --production)\n' (cd "$INSTALL_DIR" && bun install --production) # --- Drop the wrapper -------------------------------------------------------- WRAPPER="$BIN_DIR/move" printf '==> Writing wrapper to %s\n' "$WRAPPER" cat > "$WRAPPER" < "$INSTALL_DIR/.installed-version" # --- Seed user config file (only if absent) ---------------------------------- # # The defaults file shipped with the source tree (scripts/config.default.json) # is also the single source of truth for the runtime defaults loaded by # src/config.ts, so seeding a fresh user file from the same place keeps the # CLI behavior and the user-visible config in sync. # # Strict policy: never overwrite an existing user config. The uninstaller # follows the matching policy of never removing it; together that # preserves user customizations unconditionally across (re)installs and # uninstalls. assert_safe_dir "$CONFIG_DIR" SEED_SRC="$INSTALL_DIR/scripts/config.default.json" if [ ! -f "$SEED_SRC" ]; then die "default config seed missing from install tree: $SEED_SRC" fi mkdir -p "$CONFIG_DIR" if [ ! -e "$CONFIG_FILE" ]; then cp "$SEED_SRC" "$CONFIG_FILE" printf '==> Wrote default config to %s\n' "$CONFIG_FILE" else printf '==> Config already exists at %s; leaving it alone\n' "$CONFIG_FILE" fi # --- PATH sanity check ------------------------------------------------------- case ":$PATH:" in *":$BIN_DIR:"*) BIN_ON_PATH=1 ;; *) BIN_ON_PATH=0 ;; esac if [ "$BIN_ON_PATH" = "0" ]; then printf '\nNote: %s is not on your PATH. Add this to your shell rc:\n' "$BIN_DIR" printf ' export PATH="%s:$PATH"\n' "$BIN_DIR" fi # --- macOS Accessibility hint ------------------------------------------------ if [ "$OS" = "Darwin" ]; then printf '\nNote: macOS will prompt for Accessibility permission on first mouse move.\n' printf ' Grant it under System Settings > Privacy & Security > Accessibility.\n' fi # --- Final message ----------------------------------------------------------- printf '\nInstalled move %s at %s.\n' "$MOVE_VERSION" "$WRAPPER" printf "Run 'move --help' to get started.\n"