Repository navigation
feat(install): W3BS curl installer at /install.sh #356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,61 +1,159 @@ | ||
| #!/bin/sh | ||
| # Install the crawlproof CLI (npm: @profullstack/crawlproof): traffic, ads and | ||
| # money for every site you own, in a terminal. | ||
| # crawlproof installer. | ||
| # | ||
| # curl -fsSL https://crawlproof.com/install.sh | sh | ||
| # | ||
| # Uses bun if present, otherwise npm (Node >= 22.6). When the global npm | ||
| # prefix is not writable it installs into ~/.local instead of asking for sudo. | ||
| # Re-running it upgrades to the latest release. | ||
| # Installs @profullstack/crawlproof (commands: crawlproof) under your home directory. No root, no system | ||
| # package manager, no system files touched. If this machine has no Node 22+, | ||
| # an official Node build is fetched (checksum verified) into the same private directory | ||
| # and used from there. Run this again to update. Remove with: | ||
| # sh ~/.local/share/crawlproof/uninstall.sh | ||
| # | ||
| # The package is scoped: the unscoped `crawlproof` on npm is someone else's. | ||
| set -eu | ||
|
|
||
| PKG="@profullstack/crawlproof" | ||
| BIN="crawlproof" | ||
|
|
||
| say() { printf '%s\n' "$*"; } | ||
| die() { printf 'crawlproof install: %s\n' "$*" >&2; exit 1; } | ||
| have() { command -v "$1" >/dev/null 2>&1; } | ||
|
|
||
| say "Installing crawlproof (CrawlProof CLI)..." | ||
|
|
||
| if have bun; then | ||
| # bun add -g writes into the nearest package.json above its global dir when | ||
| # that dir has none (e.g. ~/package.json): give it its own first. | ||
| BUN_GLOBAL="${BUN_INSTALL:-$HOME/.bun}/install/global" | ||
| mkdir -p "$BUN_GLOBAL" | ||
| [ -f "$BUN_GLOBAL/package.json" ] || printf '{}\n' > "$BUN_GLOBAL/package.json" | ||
| bun add -g "$PKG@latest" | ||
| BIN_DIR="${BUN_INSTALL:-$HOME/.bun}/bin" | ||
| elif have npm && have node; then | ||
| node -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>22||(a===22&&b>=6)?0:1)' 2>/dev/null \ | ||
| || die "Node >= 22.6 required (found $(node -v)). Upgrade Node or install bun: https://bun.sh" | ||
| prefix=$(npm config get prefix) | ||
| modules="$prefix/lib/node_modules" | ||
| [ -d "$modules" ] || modules="$prefix/lib" | ||
| if [ -w "$modules" ] && [ -w "$prefix/bin" ]; then | ||
| npm install -g "$PKG@latest" | ||
| BIN_DIR="$prefix/bin" | ||
| # sh -s -- --version X install a specific release | ||
| # sh -s -- --prefix DIR install root (default: ~/.local) | ||
| # | ||
| # Listed in the W3BS catalog (https://w3bs.org/packages): `w3bs install crawlproof` runs | ||
| # this same script and `w3bs update` re-runs it. | ||
| # | ||
| # Generated from the W3BS npm CLI installer template; keep the variables below in sync | ||
| # with package.json. Nothing runs until the last line, so a truncated download is inert. | ||
|
|
||
| main() { | ||
| set -eu | ||
| PKG="@profullstack/crawlproof" | ||
| TOOL="crawlproof" | ||
| URL="https://crawlproof.com/install.sh" | ||
| NODE_MAJOR="22" | ||
| PREFIX="${INSTALL_PREFIX:-$HOME/.local}" | ||
| VERSION="" | ||
| NODE_CHOICE="${INSTALL_NODE:-auto}" | ||
|
|
||
| while [ $# -gt 0 ]; do | ||
| case "$1" in | ||
| --version) VERSION="${2:?--version needs a value}"; shift ;; | ||
| --prefix) PREFIX="${2:?--prefix needs a value}"; shift ;; | ||
| -h | --help) echo "Usage: curl -fsSL $URL | sh [-s -- --version X --prefix DIR]"; return 0 ;; | ||
| *) echo "$TOOL installer: unknown option $1" >&2; return 64 ;; | ||
| esac | ||
| shift | ||
| done | ||
|
|
||
| say() { printf '%s\n' "$*"; } | ||
| fail() { printf '%s installer: %s\n' "$TOOL" "$*" >&2; exit 1; } | ||
|
|
||
| case "$(uname -s)" in | ||
| Linux) OS=linux ;; | ||
| Darwin) OS=darwin ;; | ||
| *) fail "unsupported operating system $(uname -s); Linux and macOS are supported" ;; | ||
| esac | ||
| case "$(uname -m)" in | ||
| x86_64 | amd64) ARCH=x64 ;; | ||
| aarch64 | arm64) ARCH=arm64 ;; | ||
| *) fail "unsupported CPU $(uname -m)" ;; | ||
| esac | ||
| if command -v curl >/dev/null 2>&1; then | ||
| fetch() { curl --proto '=https' --tlsv1.2 -fsSL "$1" -o "$2"; } | ||
| elif command -v wget >/dev/null 2>&1; then | ||
| fetch() { wget --https-only -q "$1" -O "$2"; } | ||
| else | ||
| BIN_DIR="$HOME/.local/bin" | ||
| say "npm prefix $prefix is not writable; installing into $HOME/.local" | ||
| npm install -g --prefix "$HOME/.local" "$PKG@latest" | ||
| fail "curl or wget is required" | ||
| fi | ||
| else | ||
| die "needs bun (https://bun.sh) or Node >= 22.6 with npm (https://nodejs.org)" | ||
| fi | ||
|
|
||
| say "" | ||
| if have "$BIN"; then | ||
| say "crawlproof $("$BIN" --version 2>/dev/null || echo '') installed." | ||
| else | ||
| say "crawlproof installed to $BIN_DIR, which is not on your PATH. Add it:" | ||
| say " export PATH=\"$BIN_DIR:\$PATH\"" | ||
| fi | ||
| say "" | ||
| say "Next: create an API token at https://crawlproof.com/dashboard (Social -> API tokens), then" | ||
| say " export CRAWLPROOF_TOKEN=crp_... # or put { \"token\": \"crp_...\" } in ~/.crawlproof.json" | ||
| say " crawlproof dashboard # ROI, Traffic, Ads, Money, Spend" | ||
| say " crawlproof stats example.com # one site as text" | ||
| say " crawlproof help # every command" | ||
|
|
||
| SHARE="$PREFIX/share/$TOOL" | ||
| BIN="$PREFIX/bin" | ||
| mkdir -p "$SHARE" "$BIN" | ||
|
|
||
| node_major() { "$1" -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0; } | ||
| NODE="" | ||
| NODE_KIND="" | ||
| if [ "$NODE_CHOICE" != private ] && command -v node >/dev/null 2>&1 && [ "$(node_major node)" -ge "$NODE_MAJOR" ]; then | ||
| NODE="$(command -v node)" | ||
| NODE_KIND=system | ||
| elif [ -x "$SHARE/node/bin/node" ] && [ "$(node_major "$SHARE/node/bin/node")" -ge "$NODE_MAJOR" ]; then | ||
| NODE="$SHARE/node/bin/node" | ||
| NODE_KIND=private | ||
| fi | ||
| if [ -z "$NODE" ]; then | ||
| say "No Node $NODE_MAJOR+ here; fetching an official Node 24 into $SHARE/node ..." | ||
| TMP="$(mktemp -d)" | ||
| trap 'rm -rf "$TMP"' EXIT | ||
| fetch "https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt" "$TMP/SHASUMS256.txt" || fail "cannot reach nodejs.org" | ||
| TARBALL="$(grep -o "node-v24\.[0-9.]*-$OS-$ARCH\.tar\.gz" "$TMP/SHASUMS256.txt" | head -n 1 || true)" | ||
| [ -n "$TARBALL" ] || fail "nodejs.org has no Node 24 build for $OS-$ARCH" | ||
| fetch "https://nodejs.org/dist/latest-v24.x/$TARBALL" "$TMP/$TARBALL" || fail "cannot download $TARBALL" | ||
| if command -v sha256sum >/dev/null 2>&1; then SUM="$(sha256sum "$TMP/$TARBALL" | cut -d ' ' -f 1)" | ||
| else SUM="$(shasum -a 256 "$TMP/$TARBALL" | cut -d ' ' -f 1)"; fi | ||
| grep -q "^$SUM $TARBALL\$" "$TMP/SHASUMS256.txt" || fail "checksum mismatch for $TARBALL" | ||
| rm -rf "$SHARE/node" && mkdir -p "$SHARE/node" | ||
| tar -xzf "$TMP/$TARBALL" -C "$SHARE/node" --strip-components=1 || fail "cannot unpack $TARBALL" | ||
| NODE="$SHARE/node/bin/node" | ||
| NODE_KIND=private | ||
| fi | ||
| if [ "$NODE_KIND" = private ]; then NPM="$SHARE/node/bin/npm"; else NPM="$(dirname "$NODE")/npm"; [ -x "$NPM" ] || NPM=npm; fi | ||
| # npm runs package scripts with the Node in PATH; make that the chosen one. | ||
| PATH="$(dirname "$NODE"):$PATH" | ||
| export PATH | ||
|
|
||
| SPEC="$PKG" | ||
| [ -n "$VERSION" ] && SPEC="$PKG@$VERSION" | ||
| say "Installing $SPEC into $SHARE ..." | ||
| rm -rf "$SHARE/node_modules" "$SHARE/package.json" "$SHARE/package-lock.json" | ||
| cd "$SHARE" | ||
| printf '{ "name": "%s-install", "private": true }\n' "$TOOL" > package.json | ||
| "$NPM" install --silent --no-audit --no-fund --omit=dev "$SPEC" >/dev/null 2>&1 || | ||
| "$NPM" install --no-audit --no-fund --omit=dev "$SPEC" || fail "npm could not install $SPEC" | ||
| PKG_DIR="$SHARE/node_modules/$PKG" | ||
| [ -f "$PKG_DIR/package.json" ] || fail "$SPEC installed but $PKG_DIR is missing" | ||
|
|
||
| # One shim per entry in the package's own `bin` map: a script, not a symlink, so the | ||
| # command always runs this copy with the chosen Node. | ||
| BINS="$("$NODE" -e ' | ||
| const p = require(process.argv[1] + "/package.json"); | ||
| const b = typeof p.bin === "string" ? { [p.name.split("/").pop()]: p.bin } : (p.bin || {}); | ||
| for (const [k, v] of Object.entries(b)) if (/^[A-Za-z0-9._-]+$/.test(k)) console.log(k + "\t" + v); | ||
| ' "$PKG_DIR")" | ||
| [ -n "$BINS" ] || fail "$SPEC declares no commands" | ||
| PATHS="$SHARE" | ||
| NAMES="" | ||
| OLDIFS="$IFS" | ||
| IFS=' | ||
| ' | ||
| for line in $BINS; do | ||
| name="${line%% *}" | ||
| rel="${line#* }" | ||
| entry="$PKG_DIR/$rel" | ||
| [ -f "$entry" ] || fail "$SPEC is missing $rel" | ||
| printf '#!/bin/sh\n# %s, installed by the %s installer (%s).\nexec "%s" "%s" "$@"\n' "$name" "$TOOL" "$URL" "$NODE" "$entry" > "$BIN/$name" | ||
| chmod 0755 "$BIN/$name" | ||
| PATHS="$PATHS | ||
| $BIN/$name" | ||
| NAMES="$NAMES $name" | ||
| done | ||
| IFS="$OLDIFS" | ||
|
|
||
| INSTALLED="$("$NODE" -p "require('$PKG_DIR/package.json').version" 2>/dev/null || echo unknown)" | ||
| { | ||
| printf '{\n "package": "%s",\n "version": "%s",\n "installer": "%s",\n' "$PKG" "$INSTALLED" "$URL" | ||
| printf ' "installedAt": "%s",\n "node": "%s",\n "paths": [\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$NODE_KIND" | ||
| printf '%s\n' "$PATHS" | sed 's/.*/ "&",/' | sed '$ s/,$//' | ||
| printf ' ]\n}\n' | ||
| } > "$SHARE/manifest.json" | ||
| { | ||
| echo '#!/bin/sh' | ||
| echo "# Removes $TOOL. Written by its installer." | ||
| echo 'set -eu' | ||
| printf '%s\n' "$PATHS" | sed 's|.*|rm -rf "&"|' | ||
| echo "echo '$TOOL removed.'" | ||
| } > "$SHARE/uninstall.sh" | ||
| chmod 0755 "$SHARE/uninstall.sh" | ||
|
|
||
| say "" | ||
| say "Installed $PKG $INSTALLED:$NAMES (in $BIN)" | ||
| case ":$PATH:" in | ||
| *":$BIN:"*) ;; | ||
| *) say "Add $BIN to your PATH: echo 'export PATH=\"$BIN:\$PATH\"' >> ~/.profile" ;; | ||
| esac | ||
| say "Update by running the installer again; remove with: sh $SHARE/uninstall.sh" | ||
| } | ||
|
|
||
| main "$@" | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.