#!/bin/sh # # SiftDrive agent installer. curl -fsSL https://siftdrive.com/install.sh | sh # # READ THIS BEFORE RUNNING IT. That is not a formality: piping a script from # the internet into a shell is the install method a careful person objects to, # and SiftDrive's whole pitch is being careful with your files. So this script # is written to be read in one sitting, and to behave the way it says. # # * It does not use sudo and does not ask for your password. # * It installs ONE file, into ~/.local/bin, and tells you the path. # * It verifies a SHA-256 checksum before anything is installed. # * It never runs the thing it downloaded. # * It touches nothing else. Uninstalling is deleting one file. # # EVERYTHING IS INSIDE A FUNCTION CALLED ON THE LAST LINE. That is the reason, # and it is the one thing about a piped installer that actually matters: if # your connection drops halfway through the download, `sh` executes the half it # received. A truncated script that is all function definitions and no call # does nothing at all, which is the only safe way to be cut in half. set -eu SIFTDRIVE_BASE="${SIFTDRIVE_BASE:-https://www.siftdrive.com/download}" SIFTDRIVE_PREFIX="${SIFTDRIVE_PREFIX:-$HOME/.local/bin}" main() { say() { printf '%s\n' "$*"; } die() { printf '\n%s\n' "$*" >&2; exit 1; } # ROOT IS REFUSED. Nothing here needs it, a root install writes outside your # own directory, and a script that quietly accepts root is a script that # would have used it. if [ "$(id -u)" = "0" ]; then die "Do not run this as root. It installs one file into your own ~/.local/bin and needs no special permission. If you meant to install it system-wide, copy the file there yourself afterwards." fi command -v curl >/dev/null 2>&1 || die "This needs curl, which your system does not have." # NODE IS A REAL REQUIREMENT AND IS SAID PLAINLY. The agent is one JavaScript # file; it needs a runtime. Telling you how to get it is better than # installing something on your behalf that you did not ask for. if ! command -v node >/dev/null 2>&1; then die "SiftDrive needs Node.js 20 or newer, which is not installed. macOS: brew install node Linux: your package manager, or https://nodejs.org Then run this again. We deliberately do not install it for you." fi node_major=$(node -e 'process.stdout.write(String(process.versions.node.split(".")[0]))' 2>/dev/null || echo 0) if [ "$node_major" -lt 20 ]; then die "SiftDrive needs Node.js 20 or newer. You have $(node -v)." fi tmp=$(mktemp -d 2>/dev/null || mktemp -d -t siftdrive) # Cleans up on failure and on Ctrl-C, not only on success. trap 'rm -rf "$tmp"' EXIT INT TERM say "Downloading the SiftDrive agent..." # TIMEOUTS, because without them a stalled connection hangs forever and the # person is left staring at "Downloading..." with no way to tell a slow # network from a dead one. Found by a test that would not finish. fetch() { curl -fsSL --proto '=https' --tlsv1.2 --connect-timeout 15 --max-time 120 --retry 2 "$1" -o "$2" } fetch "$SIFTDRIVE_BASE/siftdrive.mjs" "$tmp/siftdrive.mjs" \ || die "Download failed. Nothing has been installed." fetch "$SIFTDRIVE_BASE/siftdrive.mjs.sha256" "$tmp/sha" \ || die "Could not fetch the checksum. Nothing has been installed." # VERIFY BEFORE INSTALLING, and refuse rather than warn. A checksum that only # prints a warning is decoration. expected=$(cut -d' ' -f1 < "$tmp/sha") if command -v shasum >/dev/null 2>&1; then actual=$(shasum -a 256 "$tmp/siftdrive.mjs" | cut -d' ' -f1) elif command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$tmp/siftdrive.mjs" | cut -d' ' -f1) else die "No shasum or sha256sum on this system, so the download cannot be verified. Nothing has been installed. Download it by hand from $SIFTDRIVE_BASE if you want to check it yourself." fi [ -n "$expected" ] || die "The checksum file was empty. Nothing has been installed." if [ "$actual" != "$expected" ]; then die "CHECKSUM MISMATCH. Nothing has been installed. expected $expected got $actual The download was corrupted or tampered with. Please tell us: justin@jackedtrade.com" fi mkdir -p "$SIFTDRIVE_PREFIX" # Written to a temporary name in the destination and moved into place, so an # interrupted install cannot leave a half-written executable where a working # one used to be. cp "$tmp/siftdrive.mjs" "$SIFTDRIVE_PREFIX/.siftdrive.incoming" chmod 755 "$SIFTDRIVE_PREFIX/.siftdrive.incoming" mv "$SIFTDRIVE_PREFIX/.siftdrive.incoming" "$SIFTDRIVE_PREFIX/siftdrive" say "" say "Installed: $SIFTDRIVE_PREFIX/siftdrive" say "Checksum: $actual" say "Uninstall: rm $SIFTDRIVE_PREFIX/siftdrive" say "" # SAYS SO RATHER THAN EDITING YOUR SHELL PROFILE. Writing to someone's # .zshrc without asking is exactly the liberty this script is trying not to # take, and a line appended there outlives the uninstall above. # # And when the prefix is NOT on PATH, the commands printed below are the full # path, not the bare name. Printing "siftdrive volumes" to somebody for whom # that is command-not-found is worse than printing nothing: it reads as a # broken install rather than a PATH they have not set yet. cmd="siftdrive" case ":$PATH:" in *":$SIFTDRIVE_PREFIX:"*) ;; *) cmd="$SIFTDRIVE_PREFIX/siftdrive" say "$SIFTDRIVE_PREFIX is not on your PATH, so use the full path below," say "or add this to your shell profile and open a new terminal:" say "" say " export PATH=\"\$PATH:$SIFTDRIVE_PREFIX\"" say "" say "We have not edited any file of yours to do it." say "" ;; esac say "Next: $cmd volumes # list the drives it can see" say " $cmd scan /Volumes/YourDrive" say "" say "The scan is read-only and free. Nothing moves until you run 'apply --yes'." } main "$@"