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.

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