Files
Move/scripts/install.sh
T
nokeo08 7714a6ad1a Seed default config on install; share defaults JSON with runtime
The defaults now live in a single file, scripts/config.default.json:

- src/config.ts imports it via 'with { type: "json" }' and derives
  DEFAULT_CONFIG (with seconds->ms conversion at the boundary), so the
  CLI help text always matches what the seeded user config contains.
- scripts/install.sh copies the same file to
  $XDG_CONFIG_HOME/move/config.json only if no file is already there.
  Existing configs (yours or from a previous install) are never
  overwritten.
- scripts/uninstall.sh does not touch the user config file at all,
  following the strict Unix convention. It does print a one-line
  notice pointing at the path so the user can rm -rf the dir
  themselves if they want a fully-clean removal.
- tsconfig.json gains resolveJsonModule:true so tsc accepts the JSON
  import.
- A small runtime assertion in src/config.ts (assertSeedShape) fails
  loudly if the seed file is missing keys or has wrong types.
- README Configuration section documents the seed behavior; Uninstall
  section documents the leave-config-behind policy with the rm-it-
  yourself command; Files table gains scripts/config.default.json.
2026-06-17 13:13:49 -05:00

211 lines
7.0 KiB
Bash
Executable File

#!/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 <<EOF
Error: bun is not installed.
Install it from https://bun.sh
(e.g. 'curl -fsSL https://bun.sh/install | bash')
then re-run this installer.
EOF
exit 1
fi
BUN_VERSION=$(bun --version)
printf '==> 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" <<EOF
#!/usr/bin/env sh
exec bun "$INSTALL_DIR/src/move.ts" "\$@"
EOF
chmod +x "$WRAPPER"
# --- Write version marker ----------------------------------------------------
printf '%s\n' "$MOVE_VERSION" > "$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"