|
1 | 1 | #!/bin/sh |
2 | | -# Install the crawlproof CLI (npm: @profullstack/crawlproof): traffic, ads and |
3 | | -# money for every site you own, in a terminal. |
| 2 | +# crawlproof installer. |
4 | 3 | # |
5 | 4 | # curl -fsSL https://crawlproof.com/install.sh | sh |
6 | 5 | # |
7 | | -# Uses bun if present, otherwise npm (Node >= 22.6). When the global npm |
8 | | -# prefix is not writable it installs into ~/.local instead of asking for sudo. |
9 | | -# Re-running it upgrades to the latest release. |
| 6 | +# Installs @profullstack/crawlproof (commands: crawlproof) under your home directory. No root, no system |
| 7 | +# package manager, no system files touched. If this machine has no Node 22+, |
| 8 | +# an official Node build is fetched (checksum verified) into the same private directory |
| 9 | +# and used from there. Run this again to update. Remove with: |
| 10 | +# sh ~/.local/share/crawlproof/uninstall.sh |
10 | 11 | # |
11 | | -# The package is scoped: the unscoped `crawlproof` on npm is someone else's. |
12 | | -set -eu |
13 | | - |
14 | | -PKG="@profullstack/crawlproof" |
15 | | -BIN="crawlproof" |
16 | | - |
17 | | -say() { printf '%s\n' "$*"; } |
18 | | -die() { printf 'crawlproof install: %s\n' "$*" >&2; exit 1; } |
19 | | -have() { command -v "$1" >/dev/null 2>&1; } |
20 | | - |
21 | | -say "Installing crawlproof (CrawlProof CLI)..." |
22 | | - |
23 | | -if have bun; then |
24 | | - # bun add -g writes into the nearest package.json above its global dir when |
25 | | - # that dir has none (e.g. ~/package.json): give it its own first. |
26 | | - BUN_GLOBAL="${BUN_INSTALL:-$HOME/.bun}/install/global" |
27 | | - mkdir -p "$BUN_GLOBAL" |
28 | | - [ -f "$BUN_GLOBAL/package.json" ] || printf '{}\n' > "$BUN_GLOBAL/package.json" |
29 | | - bun add -g "$PKG@latest" |
30 | | - BIN_DIR="${BUN_INSTALL:-$HOME/.bun}/bin" |
31 | | -elif have npm && have node; then |
32 | | - node -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>22||(a===22&&b>=6)?0:1)' 2>/dev/null \ |
33 | | - || die "Node >= 22.6 required (found $(node -v)). Upgrade Node or install bun: https://bun.sh" |
34 | | - prefix=$(npm config get prefix) |
35 | | - modules="$prefix/lib/node_modules" |
36 | | - [ -d "$modules" ] || modules="$prefix/lib" |
37 | | - if [ -w "$modules" ] && [ -w "$prefix/bin" ]; then |
38 | | - npm install -g "$PKG@latest" |
39 | | - BIN_DIR="$prefix/bin" |
| 12 | +# sh -s -- --version X install a specific release |
| 13 | +# sh -s -- --prefix DIR install root (default: ~/.local) |
| 14 | +# |
| 15 | +# Listed in the W3BS catalog (https://w3bs.org/packages): `w3bs install crawlproof` runs |
| 16 | +# this same script and `w3bs update` re-runs it. |
| 17 | +# |
| 18 | +# Generated from the W3BS npm CLI installer template; keep the variables below in sync |
| 19 | +# with package.json. Nothing runs until the last line, so a truncated download is inert. |
| 20 | + |
| 21 | +main() { |
| 22 | + set -eu |
| 23 | + PKG="@profullstack/crawlproof" |
| 24 | + TOOL="crawlproof" |
| 25 | + URL="https://crawlproof.com/install.sh" |
| 26 | + NODE_MAJOR="22" |
| 27 | + PREFIX="${INSTALL_PREFIX:-$HOME/.local}" |
| 28 | + VERSION="" |
| 29 | + NODE_CHOICE="${INSTALL_NODE:-auto}" |
| 30 | + |
| 31 | + while [ $# -gt 0 ]; do |
| 32 | + case "$1" in |
| 33 | + --version) VERSION="${2:?--version needs a value}"; shift ;; |
| 34 | + --prefix) PREFIX="${2:?--prefix needs a value}"; shift ;; |
| 35 | + -h | --help) echo "Usage: curl -fsSL $URL | sh [-s -- --version X --prefix DIR]"; return 0 ;; |
| 36 | + *) echo "$TOOL installer: unknown option $1" >&2; return 64 ;; |
| 37 | + esac |
| 38 | + shift |
| 39 | + done |
| 40 | + |
| 41 | + say() { printf '%s\n' "$*"; } |
| 42 | + fail() { printf '%s installer: %s\n' "$TOOL" "$*" >&2; exit 1; } |
| 43 | + |
| 44 | + case "$(uname -s)" in |
| 45 | + Linux) OS=linux ;; |
| 46 | + Darwin) OS=darwin ;; |
| 47 | + *) fail "unsupported operating system $(uname -s); Linux and macOS are supported" ;; |
| 48 | + esac |
| 49 | + case "$(uname -m)" in |
| 50 | + x86_64 | amd64) ARCH=x64 ;; |
| 51 | + aarch64 | arm64) ARCH=arm64 ;; |
| 52 | + *) fail "unsupported CPU $(uname -m)" ;; |
| 53 | + esac |
| 54 | + if command -v curl >/dev/null 2>&1; then |
| 55 | + fetch() { curl --proto '=https' --tlsv1.2 -fsSL "$1" -o "$2"; } |
| 56 | + elif command -v wget >/dev/null 2>&1; then |
| 57 | + fetch() { wget --https-only -q "$1" -O "$2"; } |
40 | 58 | else |
41 | | - BIN_DIR="$HOME/.local/bin" |
42 | | - say "npm prefix $prefix is not writable; installing into $HOME/.local" |
43 | | - npm install -g --prefix "$HOME/.local" "$PKG@latest" |
| 59 | + fail "curl or wget is required" |
44 | 60 | fi |
45 | | -else |
46 | | - die "needs bun (https://bun.sh) or Node >= 22.6 with npm (https://nodejs.org)" |
47 | | -fi |
48 | | - |
49 | | -say "" |
50 | | -if have "$BIN"; then |
51 | | - say "crawlproof $("$BIN" --version 2>/dev/null || echo '') installed." |
52 | | -else |
53 | | - say "crawlproof installed to $BIN_DIR, which is not on your PATH. Add it:" |
54 | | - say " export PATH=\"$BIN_DIR:\$PATH\"" |
55 | | -fi |
56 | | -say "" |
57 | | -say "Next: create an API token at https://crawlproof.com/dashboard (Social -> API tokens), then" |
58 | | -say " export CRAWLPROOF_TOKEN=crp_... # or put { \"token\": \"crp_...\" } in ~/.crawlproof.json" |
59 | | -say " crawlproof dashboard # ROI, Traffic, Ads, Money, Spend" |
60 | | -say " crawlproof stats example.com # one site as text" |
61 | | -say " crawlproof help # every command" |
| 61 | + |
| 62 | + SHARE="$PREFIX/share/$TOOL" |
| 63 | + BIN="$PREFIX/bin" |
| 64 | + mkdir -p "$SHARE" "$BIN" |
| 65 | + |
| 66 | + node_major() { "$1" -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0; } |
| 67 | + NODE="" |
| 68 | + NODE_KIND="" |
| 69 | + if [ "$NODE_CHOICE" != private ] && command -v node >/dev/null 2>&1 && [ "$(node_major node)" -ge "$NODE_MAJOR" ]; then |
| 70 | + NODE="$(command -v node)" |
| 71 | + NODE_KIND=system |
| 72 | + elif [ -x "$SHARE/node/bin/node" ] && [ "$(node_major "$SHARE/node/bin/node")" -ge "$NODE_MAJOR" ]; then |
| 73 | + NODE="$SHARE/node/bin/node" |
| 74 | + NODE_KIND=private |
| 75 | + fi |
| 76 | + if [ -z "$NODE" ]; then |
| 77 | + say "No Node $NODE_MAJOR+ here; fetching an official Node 24 into $SHARE/node ..." |
| 78 | + TMP="$(mktemp -d)" |
| 79 | + trap 'rm -rf "$TMP"' EXIT |
| 80 | + fetch "https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt" "$TMP/SHASUMS256.txt" || fail "cannot reach nodejs.org" |
| 81 | + TARBALL="$(grep -o "node-v24\.[0-9.]*-$OS-$ARCH\.tar\.gz" "$TMP/SHASUMS256.txt" | head -n 1 || true)" |
| 82 | + [ -n "$TARBALL" ] || fail "nodejs.org has no Node 24 build for $OS-$ARCH" |
| 83 | + fetch "https://nodejs.org/dist/latest-v24.x/$TARBALL" "$TMP/$TARBALL" || fail "cannot download $TARBALL" |
| 84 | + if command -v sha256sum >/dev/null 2>&1; then SUM="$(sha256sum "$TMP/$TARBALL" | cut -d ' ' -f 1)" |
| 85 | + else SUM="$(shasum -a 256 "$TMP/$TARBALL" | cut -d ' ' -f 1)"; fi |
| 86 | + grep -q "^$SUM $TARBALL\$" "$TMP/SHASUMS256.txt" || fail "checksum mismatch for $TARBALL" |
| 87 | + rm -rf "$SHARE/node" && mkdir -p "$SHARE/node" |
| 88 | + tar -xzf "$TMP/$TARBALL" -C "$SHARE/node" --strip-components=1 || fail "cannot unpack $TARBALL" |
| 89 | + NODE="$SHARE/node/bin/node" |
| 90 | + NODE_KIND=private |
| 91 | + fi |
| 92 | + if [ "$NODE_KIND" = private ]; then NPM="$SHARE/node/bin/npm"; else NPM="$(dirname "$NODE")/npm"; [ -x "$NPM" ] || NPM=npm; fi |
| 93 | + # npm runs package scripts with the Node in PATH; make that the chosen one. |
| 94 | + PATH="$(dirname "$NODE"):$PATH" |
| 95 | + export PATH |
| 96 | + |
| 97 | + SPEC="$PKG" |
| 98 | + [ -n "$VERSION" ] && SPEC="$PKG@$VERSION" |
| 99 | + say "Installing $SPEC into $SHARE ..." |
| 100 | + rm -rf "$SHARE/node_modules" "$SHARE/package.json" "$SHARE/package-lock.json" |
| 101 | + cd "$SHARE" |
| 102 | + printf '{ "name": "%s-install", "private": true }\n' "$TOOL" > package.json |
| 103 | + "$NPM" install --silent --no-audit --no-fund --omit=dev "$SPEC" >/dev/null 2>&1 || |
| 104 | + "$NPM" install --no-audit --no-fund --omit=dev "$SPEC" || fail "npm could not install $SPEC" |
| 105 | + PKG_DIR="$SHARE/node_modules/$PKG" |
| 106 | + [ -f "$PKG_DIR/package.json" ] || fail "$SPEC installed but $PKG_DIR is missing" |
| 107 | + |
| 108 | + # One shim per entry in the package's own `bin` map: a script, not a symlink, so the |
| 109 | + # command always runs this copy with the chosen Node. |
| 110 | + BINS="$("$NODE" -e ' |
| 111 | + const p = require(process.argv[1] + "/package.json"); |
| 112 | + const b = typeof p.bin === "string" ? { [p.name.split("/").pop()]: p.bin } : (p.bin || {}); |
| 113 | + for (const [k, v] of Object.entries(b)) if (/^[A-Za-z0-9._-]+$/.test(k)) console.log(k + "\t" + v); |
| 114 | + ' "$PKG_DIR")" |
| 115 | + [ -n "$BINS" ] || fail "$SPEC declares no commands" |
| 116 | + PATHS="$SHARE" |
| 117 | + NAMES="" |
| 118 | + OLDIFS="$IFS" |
| 119 | + IFS=' |
| 120 | +' |
| 121 | + for line in $BINS; do |
| 122 | + name="${line%% *}" |
| 123 | + rel="${line#* }" |
| 124 | + entry="$PKG_DIR/$rel" |
| 125 | + [ -f "$entry" ] || fail "$SPEC is missing $rel" |
| 126 | + printf '#!/bin/sh\n# %s, installed by the %s installer (%s).\nexec "%s" "%s" "$@"\n' "$name" "$TOOL" "$URL" "$NODE" "$entry" > "$BIN/$name" |
| 127 | + chmod 0755 "$BIN/$name" |
| 128 | + PATHS="$PATHS |
| 129 | +$BIN/$name" |
| 130 | + NAMES="$NAMES $name" |
| 131 | + done |
| 132 | + IFS="$OLDIFS" |
| 133 | + |
| 134 | + INSTALLED="$("$NODE" -p "require('$PKG_DIR/package.json').version" 2>/dev/null || echo unknown)" |
| 135 | + { |
| 136 | + printf '{\n "package": "%s",\n "version": "%s",\n "installer": "%s",\n' "$PKG" "$INSTALLED" "$URL" |
| 137 | + printf ' "installedAt": "%s",\n "node": "%s",\n "paths": [\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$NODE_KIND" |
| 138 | + printf '%s\n' "$PATHS" | sed 's/.*/ "&",/' | sed '$ s/,$//' |
| 139 | + printf ' ]\n}\n' |
| 140 | + } > "$SHARE/manifest.json" |
| 141 | + { |
| 142 | + echo '#!/bin/sh' |
| 143 | + echo "# Removes $TOOL. Written by its installer." |
| 144 | + echo 'set -eu' |
| 145 | + printf '%s\n' "$PATHS" | sed 's|.*|rm -rf "&"|' |
| 146 | + echo "echo '$TOOL removed.'" |
| 147 | + } > "$SHARE/uninstall.sh" |
| 148 | + chmod 0755 "$SHARE/uninstall.sh" |
| 149 | + |
| 150 | + say "" |
| 151 | + say "Installed $PKG $INSTALLED:$NAMES (in $BIN)" |
| 152 | + case ":$PATH:" in |
| 153 | + *":$BIN:"*) ;; |
| 154 | + *) say "Add $BIN to your PATH: echo 'export PATH=\"$BIN:\$PATH\"' >> ~/.profile" ;; |
| 155 | + esac |
| 156 | + say "Update by running the installer again; remove with: sh $SHARE/uninstall.sh" |
| 157 | +} |
| 158 | + |
| 159 | +main "$@" |
0 commit comments