Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

143 lines · 6.0 KBRaw History
1#!/usr/bin/env bash
2# Installs the Diffinery CLI on Linux and macOS:
3#
4# curl -fsSL https://diffinery.com/install | bash
5#
6# The CLI goes in ~/.diffinery (set DIFFINERY_INSTALL to change that), and ~/.diffinery/bin is
7# added to PATH in your shell's startup file (DIFFINERY_NO_MODIFY_PATH=1 skips that). It runs on
8# Node.js 22 or later: the one on your PATH when it's new enough, otherwise a private copy of the
9# Node.js LTS from nodejs.org, checked against its published SHA-256, in ~/.diffinery/node.
10# Run it again, or `diffinery upgrade`, to update. Served by Diffinery from apps/cli/install/.
11
12# Piped into sh instead of bash, a shell like dash stops at `set -o pipefail` with no hint why.
13if [ -z "${BASH_VERSION:-}" ]; then
14 echo "This installer needs bash: curl -fsSL __DIFFINERY_URL__/install | bash" >&2
15 exit 1
16fi
17set -euo pipefail
18
19server=${DIFFINERY_URL:-__DIFFINERY_URL__}
20case $server in __*) server=https://diffinery.com ;; esac
21server=${server%/}
22root=${DIFFINERY_INSTALL:-$HOME/.diffinery}
23bin=$root/bin
24node_line=24
25
26if [ -t 2 ] && [ -z "${NO_COLOR:-}" ]; then
27 bold=$'\033[1m' dim=$'\033[2m' green=$'\033[32m' red=$'\033[31m' reset=$'\033[0m'
28else
29 bold='' dim='' green='' red='' reset=''
30fi
31say() { printf '%s\n' "$*" >&2; }
32die() {
33 say "${red}error${reset}: $*"
34 exit 1
35}
36tilde() { case $1 in "$HOME"/*) printf '~%s' "${1#"$HOME"}" ;; *) printf '%s' "$1" ;; esac; }
37
38command -v curl >/dev/null || die "curl is required"
39case $(uname -s) in
40 Linux) os=linux ;;
41 Darwin) os=darwin ;;
42 MINGW* | MSYS* | CYGWIN*) die "on Windows, run this in PowerShell instead: irm $server/install.ps1 | iex" ;;
43 *) die "$(uname -s) isn't supported by this script; install Node.js 22 or later and download $server/-/cli/diffinery.mjs" ;;
44esac
45case $(uname -m) in
46 x86_64 | amd64) arch=x64 ;;
47 arm64 | aarch64) arch=arm64 ;;
48 *) arch='' ;;
49esac
50# A shell under Rosetta on Apple silicon still gets the arm64 Node.js.
51if [ "$os" = darwin ] && [ "$arch" = x64 ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = 1 ]; then arch=arm64; fi
52
53tmp=$(mktemp -d)
54trap 'rm -rf "$tmp"' EXIT
55
56node_ok() { "$1" -e 'process.exit(Number(process.versions.node.split(".")[0]) >= 22 ? 0 : 1)' >/dev/null 2>&1; }
57
58# 1. Node.js for the CLI to run on.
59private=$root/node/bin/node
60if [ -x "$private" ] && node_ok "$private"; then
61 node=$private
62elif command -v node >/dev/null && node_ok node; then
63 node=''
64else
65 [ -n "$arch" ] || die "the CLI needs Node.js 22 or later, and nodejs.org has no build for $(uname -m); install Node.js and run this again"
66 if [ "$os" = linux ] && ldd --version 2>&1 | grep -qi musl; then die "the CLI needs Node.js 22 or later; on Alpine and other musl systems install it with your package manager (apk add nodejs) and run this again"; fi
67 say "Installing Node.js $node_line LTS for the CLI, from nodejs.org…"
68 dist=https://nodejs.org/dist/latest-v$node_line.x
69 sums=$(curl -fsSL "$dist/SHASUMS256.txt") || die "couldn't reach nodejs.org"
70 line=$(printf '%s\n' "$sums" | grep -E " node-v[0-9.]+-$os-$arch\.tar\.gz$" | head -n 1 || true)
71 [ -n "$line" ] || die "nodejs.org has no Node.js build for $os-$arch"
72 want=${line%% *}
73 file=${line##* }
74 curl -fsSL "$dist/$file" -o "$tmp/node.tar.gz" || die "couldn't download $file"
75 if command -v sha256sum >/dev/null; then got=$(sha256sum "$tmp/node.tar.gz"); else got=$(shasum -a 256 "$tmp/node.tar.gz"); fi
76 [ "${got%% *}" = "$want" ] || die "$file doesn't match its published SHA-256, so it wasn't installed"
77 mkdir -p "$tmp/node" "$root"
78 tar -xzf "$tmp/node.tar.gz" -C "$tmp/node" --strip-components 1
79 rm -rf "$root/node"
80 mv "$tmp/node" "$root/node"
81 node=$private
82fi
83
84# 2. The CLI itself: one JavaScript file.
85curl -fsSL "$server/-/cli/diffinery.mjs" -o "$tmp/diffinery.mjs" || die "couldn't download the CLI from $server"
86head -n 1 "$tmp/diffinery.mjs" | grep -q '^#!/usr/bin/env node' || die "$server/-/cli/diffinery.mjs isn't the CLI"
87mkdir -p "$root/lib" "$bin"
88chmod 755 "$tmp/diffinery.mjs"
89mv -f "$tmp/diffinery.mjs" "$root/lib/diffinery.mjs"
90
91# 3. The diffinery command: the private Node.js when there is one, else the node on PATH.
92q=$(printf '%q' "$root")
93cat >"$tmp/diffinery" <<EOF
94#!/bin/sh
95# The Diffinery CLI, installed from $server/install. Update it with: diffinery upgrade
96root=$q
97node=\$root/node/bin/node
98[ -x "\$node" ] || node=node
99exec "\$node" "\$root/lib/diffinery.mjs" "\$@"
100EOF
101chmod 755 "$tmp/diffinery"
102mv -f "$tmp/diffinery" "$bin/diffinery"
103version=$("$bin/diffinery" --version) || die "the CLI was installed in $(tilde "$root") but doesn't start; run $bin/diffinery --version to see why"
104
105# 4. PATH.
106note=''
107case ":$PATH:" in
108 *":$bin:"*) ;;
109 *)
110 if [ -z "${DIFFINERY_NO_MODIFY_PATH:-}" ]; then
111 line="export PATH=\"$bin:\$PATH\""
112 case $(basename "${SHELL:-sh}") in
113 zsh) rc=${ZDOTDIR:-$HOME}/.zshrc ;;
114 bash) if [ "$os" = darwin ]; then rc=$HOME/.bash_profile; else rc=$HOME/.bashrc; fi ;;
115 fish)
116 rc=$HOME/.config/fish/conf.d/diffinery.fish
117 line="fish_add_path \"$bin\""
118 ;;
119 *) rc=$HOME/.profile ;;
120 esac
121 if grep -qsF "$bin" "$rc"; then
122 did="$(tilde "$rc") already adds $(tilde "$bin") to PATH."
123 else
124 mkdir -p "$(dirname "$rc")"
125 printf '\n# Diffinery CLI\n%s\n' "$line" >>"$rc"
126 did="Added $(tilde "$bin") to PATH in $(tilde "$rc")."
127 fi
128 note="$did Open a new terminal, or run: ${bold}export PATH=\"$bin:\$PATH\"${reset}"
129 else
130 note="Add $(tilde "$bin") to your PATH to run diffinery."
131 fi
132 ;;
133esac
134
135say "${green}✓${reset} ${version} installed to $(tilde "$bin/diffinery")${node:+ ${dim}(with its own Node.js)${reset}}"
136[ -z "$note" ] || say "$note"
137other=$(command -v diffinery 2>/dev/null || true)
138if [ -n "$other" ] && [ "$other" != "$bin/diffinery" ]; then say "${dim}Another diffinery at $(tilde "$other") comes first on your PATH; remove it to use this one.${reset}"; fi
139login="diffinery login"
140[ "$server" = https://diffinery.com ] || login="diffinery login --url $server"
141say ""
142say "Next, sign in through your browser:"
143say " ${bold}$login${reset}"