#!/bin/sh set -eu repository_url=${KLINK_REPOSITORY_URL:-https://gitea.bytevibe.dev/gary/klink} api_url=${KLINK_API_URL:-https://gitea.bytevibe.dev/api/v1/repos/gary/klink/releases/latest} requested_version=${KLINK_VERSION:-} install_dir=${KLINK_INSTALL_DIR:-} say() { printf '%s\n' "$*" } fail() { printf 'klink installer: %s\n' "$*" >&2 exit 1 } require_command() { command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1" } fetch_stdout() { url=$1 case "$url" in https://*) curl -fsSL --proto '=https' --tlsv1.2 "$url" ;; *) [ "${KLINK_ALLOW_INSECURE:-}" = "1" ] || fail "refusing non-HTTPS URL: $url" curl -fsSL "$url" ;; esac } fetch_file() { url=$1 output=$2 case "$url" in https://*) curl -fsSL --proto '=https' --tlsv1.2 -o "$output" "$url" ;; *) [ "${KLINK_ALLOW_INSECURE:-}" = "1" ] || fail "refusing non-HTTPS URL: $url" curl -fsSL -o "$output" "$url" ;; esac } require_command curl require_command tar require_command awk require_command sed require_command mktemp require_command tr if [ -n "${KLINK_OS:-}" ]; then target_os=$KLINK_OS else case "$(uname -s)" in Darwin) target_os=darwin ;; Linux) target_os=linux ;; *) fail "unsupported operating system: $(uname -s)" ;; esac fi if [ -n "${KLINK_ARCH:-}" ]; then machine=$KLINK_ARCH else machine=$(uname -m) fi case "$machine" in x86_64|amd64) target_arch=amd64 ;; arm64|aarch64) target_arch=arm64 ;; *) fail "unsupported architecture: $machine" ;; esac if [ -n "$requested_version" ]; then case "$requested_version" in v*) tag=$requested_version ;; *) tag=v$requested_version ;; esac else release_json=$(fetch_stdout "$api_url") || fail "could not read the latest Gitea release" tag=$(printf '%s' "$release_json" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') [ -n "$tag" ] || fail "latest Gitea release has no tag" fi version=${tag#v} [ -n "$version" ] || fail "invalid release tag: $tag" asset=klink_${version}_${target_os}_${target_arch}.tar.gz download_base=${repository_url%/}/releases/download/$tag temporary=$(mktemp -d "${TMPDIR:-/tmp}/klink-install.XXXXXX") || fail "could not create a temporary directory" cleanup() { rm -rf "$temporary" } trap cleanup EXIT HUP INT TERM archive=$temporary/$asset checksums=$temporary/checksums.txt say "Downloading klink $tag for $target_os/$target_arch..." fetch_file "$download_base/$asset" "$archive" fetch_file "$download_base/checksums.txt" "$checksums" expected=$(awk -v name="$asset" '{ file=$2; sub(/^\*/, "", file); if (file == name) { print $1; exit } }' "$checksums") [ -n "$expected" ] || fail "$asset is not listed in checksums.txt" if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$archive" | awk '{print $1}') elif command -v shasum >/dev/null 2>&1; then actual=$(shasum -a 256 "$archive" | awk '{print $1}') else fail "sha256sum or shasum is required" fi [ "$(printf '%s' "$actual" | tr 'A-F' 'a-f')" = "$(printf '%s' "$expected" | tr 'A-F' 'a-f')" ] || fail "SHA256 checksum mismatch for $asset" extract_dir=$temporary/extract mkdir -p "$extract_dir" tar -xzf "$archive" -C "$extract_dir" binary=$extract_dir/klink [ -f "$binary" ] || fail "release archive does not contain klink" if [ -z "$install_dir" ]; then if [ -d /usr/local/bin ] && [ -w /usr/local/bin ]; then install_dir=/usr/local/bin else install_dir=$HOME/.local/bin fi fi mkdir -p "$install_dir" destination=$install_dir/klink staged=$install_dir/.klink-install.$$ cp "$binary" "$staged" chmod 755 "$staged" mv -f "$staged" "$destination" say "Installed klink $tag to $destination" case ":${PATH:-}:" in *:"$install_dir":*) ;; *) say "Add $install_dir to PATH, for example:" say " export PATH=\"$install_dir:\$PATH\"" ;; esac if ! command -v ssh >/dev/null 2>&1; then say "Warning: OpenSSH was not found in PATH; install it before connecting to nodes." fi