Files
ink-releases/install.sh
T
2026-08-10 23:31:23 +02:00

176 lines
7.3 KiB
Bash
Executable File

#!/bin/sh
set -eu
INK_VERSION=${INK_VERSION:-main}
TOOLSET_VERSION=${TOOLSET_VERSION:-main}
PREFIX=${INK_PREFIX:-$HOME/.ink}
RELEASE_BASE=${INK_RELEASE_BASE:-https://git.tmk241.com/tmk241/ink-releases/raw/branch/main}
TARGET=x86_64-linux-musl
SETS=
TOOLS=
ASSUME_YES=0
LIST_ONLY=0
usage() {
cat <<'HELP'
Usage: install.sh [OPTIONS]
Install the static Ink core and optionally selected permission-sized tools.
With a terminal and no selection flags, a small set menu is shown. Without a
terminal, the safe default installs only Ink.
Options:
--set NAME Add text, filesystem, web, git, or all (repeatable)
--tool NAME Add one exact executable (repeatable)
--version VERSION Use immutable VERSION for Ink and Ink Toolset
--ink-version VERSION Select main or an immutable Ink release
--toolset-version VER Select main or an immutable Toolset release
--prefix DIR Install executable files under DIR/bin
--list List available tool assets; do not install
-y, --yes Do not ask for confirmation
-h, --help Show this help
Environment:
INK_VERSION, TOOLSET_VERSION, INK_PREFIX (versions default to main)
INK_RELEASE_BASE (public ink-releases raw root)
Examples:
curl -fsSL URL/install.sh | sh
curl -fsSL URL/install.sh | sh -s -- --set text --set web -y
curl -fsSL URL/install.sh | sh -s -- --tool lines --tool httpget -y
Effects:
Writes executable files only under PREFIX/bin. It never edits PATH, shell
configuration, Ink policy, credentials, or unrelated state.
HELP
}
append_word() {
if [ -z "$1" ]; then printf '%s' "$2"; else printf '%s %s' "$1" "$2"; fi
}
while [ $# -gt 0 ]; do
case $1 in
--set) [ $# -ge 2 ] || { echo 'install: --set needs a value' >&2; exit 2; }; SETS=$(append_word "$SETS" "$2"); shift 2 ;;
--tool) [ $# -ge 2 ] || { echo 'install: --tool needs a value' >&2; exit 2; }; TOOLS=$(append_word "$TOOLS" "$2"); shift 2 ;;
--version) [ $# -ge 2 ] || { echo 'install: --version needs a value' >&2; exit 2; }; INK_VERSION=$2; TOOLSET_VERSION=$2; shift 2 ;;
--ink-version) [ $# -ge 2 ] || { echo 'install: --ink-version needs a value' >&2; exit 2; }; INK_VERSION=$2; shift 2 ;;
--toolset-version) [ $# -ge 2 ] || { echo 'install: --toolset-version needs a value' >&2; exit 2; }; TOOLSET_VERSION=$2; shift 2 ;;
--prefix) [ $# -ge 2 ] || { echo 'install: --prefix needs a value' >&2; exit 2; }; PREFIX=$2; shift 2 ;;
--list) LIST_ONLY=1; shift ;;
-y|--yes) ASSUME_YES=1; shift ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
*) echo "install: unknown option: $1" >&2; exit 2 ;;
esac
done
[ $# -eq 0 ] || { echo 'install: trailing arguments' >&2; exit 2; }
case $(uname -m) in x86_64|amd64) ;; *) echo 'install: this release supports x86_64 Linux only' >&2; exit 1 ;; esac
case $PREFIX in ''|/) echo 'install: unsafe empty/root prefix' >&2; exit 2 ;; esac
TTY=0
if [ -r /dev/tty ] && [ -w /dev/tty ]; then TTY=1; fi
if [ "$TTY" -eq 1 ]; then
cyan='\033[36m'; bold='\033[1m'; dim='\033[2m'; reset='\033[0m'
else
cyan= bold= dim= reset=
fi
say() { printf '%s\n' "$*" >&2; }
fetch() {
url=$1 destination=$2
if command -v curl >/dev/null 2>&1; then curl -fsSL "$url" -o "$destination"
elif command -v wget >/dev/null 2>&1; then wget -qO "$destination" "$url"
else echo 'install: curl or wget is required' >&2; exit 1
fi
}
digest() {
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}'
else echo 'install: sha256sum or shasum is required' >&2; exit 1
fi
}
if [ -z "$SETS$TOOLS" ] && [ "$TTY" -eq 1 ] && [ "$LIST_ONLY" -eq 0 ]; then
printf '%bInk%b %bsmall tools, explicit authority%b\n\n' "$bold$cyan" "$reset" "$dim" "$reset" >/dev/tty
printf ' 0 Ink only\n 1 text\n 2 filesystem\n 3 web\n 4 git\n 5 all tools\n\nSelect sets [0]: ' >/dev/tty
IFS= read -r answer </dev/tty || answer=0
for choice in $answer; do
case $choice in 0|'') ;; 1) SETS=$(append_word "$SETS" text) ;; 2) SETS=$(append_word "$SETS" filesystem) ;; 3) SETS=$(append_word "$SETS" web) ;; 4) SETS=$(append_word "$SETS" git) ;; 5) SETS=all ;; *) echo "install: invalid selection: $choice" >&2; exit 2 ;; esac
done
fi
work=${TMPDIR:-/tmp}/ink-install.$$
trap 'rm -rf "$work"' EXIT HUP INT TERM
mkdir -p "$work"
release_path() {
version=$1 kind=$2
if [ "$version" = main ]; then printf 'channels/main/%s' "$kind"
else printf 'releases/%s/%s' "$version" "$kind"
fi
}
toolset_url=$RELEASE_BASE/$(release_path "$TOOLSET_VERSION" toolset)
manifest_name=ink-toolset-$TOOLSET_VERSION-$TARGET.manifest.tsv
if [ -n "$SETS$TOOLS" ] || [ "$LIST_ONLY" -eq 1 ]; then
fetch "$toolset_url/$manifest_name" "$work/$manifest_name"
fetch "$toolset_url/ink-toolset-SHA256SUMS" "$work/toolset-SHA256SUMS"
expected=$(awk -v file="$manifest_name" '$2 == file || $2 == "*" file {print $1; exit}' "$work/toolset-SHA256SUMS")
[ -n "$expected" ] && [ "$(digest "$work/$manifest_name")" = "$expected" ] || { echo 'install: toolset manifest checksum mismatch' >&2; exit 1; }
fi
if [ "$LIST_ONLY" -eq 1 ]; then
cat "$work/$manifest_name"
exit 0
fi
selected=$work/selected.tsv
: > "$selected"
if [ -n "$SETS" ]; then
awk -F '\t' -v sets="$SETS" 'NR > 1 { n=split(sets,a," "); for(i=1;i<=n;i++) if(a[i]=="all" || $4==a[i]) {print; break} }' "$work/$manifest_name" >> "$selected"
fi
for tool in $TOOLS; do
row=$(awk -F '\t' -v tool="$tool" 'NR > 1 && $3==tool {print; exit}' "$work/$manifest_name")
[ -n "$row" ] || { echo "install: unknown tool: $tool" >&2; exit 2; }
printf '%s\n' "$row" >> "$selected"
done
sort -u "$selected" -o "$selected"
say "${cyan}Ink installer${reset}"
say " core $INK_VERSION"
say " tools $TOOLSET_VERSION ($(wc -l < "$selected" | tr -d ' ') selected)"
say " target $TARGET"
say " location $PREFIX/bin"
if [ "$ASSUME_YES" -eq 0 ] && [ "$TTY" -eq 1 ]; then
printf 'Install? [y/N] ' >/dev/tty
IFS= read -r answer </dev/tty || answer=n
case $answer in y|Y|yes|YES) ;; *) say 'install: cancelled'; exit 1 ;; esac
fi
ink_asset=ink-$TARGET
ink_url=$RELEASE_BASE/$(release_path "$INK_VERSION" ink)
fetch "$ink_url/$ink_asset" "$work/$ink_asset"
fetch "$ink_url/ink-SHA256SUMS" "$work/ink-SHA256SUMS"
expected=$(awk -v file="$ink_asset" '$2 == file || $2 == "*" file {print $1; exit}' "$work/ink-SHA256SUMS")
[ -n "$expected" ] && [ "$(digest "$work/$ink_asset")" = "$expected" ] || { echo 'install: Ink checksum mismatch' >&2; exit 1; }
while IFS="$(printf '\t')" read -r source_repo source_commit name set target bytes expected asset; do
[ -n "$name" ] || continue
fetch "$toolset_url/$asset" "$work/$asset"
[ "$(digest "$work/$asset")" = "$expected" ] || { echo "install: checksum mismatch: $asset" >&2; exit 1; }
done < "$selected"
mkdir -p "$PREFIX/bin"
chmod 755 "$work/$ink_asset"
mv "$work/$ink_asset" "$PREFIX/bin/ink"
while IFS="$(printf '\t')" read -r source_repo source_commit name set target bytes expected asset; do
[ -n "$name" ] || continue
chmod 755 "$work/$asset"
mv "$work/$asset" "$PREFIX/bin/$name"
done < "$selected"
say "${bold}Installed.${reset} Executables only; policy and PATH were not changed."
say "Run: $PREFIX/bin/ink --help"