#!/bin/sh # Installs the client-only kith on a Linux VM (a cloud bot's computer) from a GitHub Release. No git, Rust, root or sudo: # # curl -fsSL https://raw.githubusercontent.com/Ying-Kai-Liao/kith/main/scripts/install-guest.sh | sh # # Puts one static binary at ~/.local/bin/kith; then `kith join `. Run it again to upgrade. Needs sh, tar and curl (or wget), # and minisign or OpenSSL 3 to check the release's signature. # The release's SHA256SUMS is signed in CI (SHA256SUMS.minisig) with the key whose public half is KITH_PUBKEY below (the # same as packaging/minisign.pub), so a release whose files and checksums were both swapped is refused. # # Environment: # KITH_VERSION a release to install (1.2.3 or v1.2.3); default the latest # KITH_GUEST_PREFIX where to install (bin/kith goes under it); default ~/.local. Given explicitly, it also becomes the bot's # home: /.kith holds device.key and guest.json, and kith finds it from its own path in any # shell (for a VM that only keeps one folder, e.g. /workspace). KITH_GUEST_HOME still wins. # KITH_REPO GitHub repo to download from; default Ying-Kai-Liao/kith # KITH_ALLOW_UNSIGNED 1 installs a release with no signature (or no tool to check it) on the checksum alone # KITH_INSTALL_TARBALL a local kith-guest--linux-.tar.gz to install instead of downloading (tests, # offline); verified against KITH_INSTALL_SHA256 or the .sha256 file beside it, if there is one set -eu REPO=${KITH_REPO:-Ying-Kai-Liao/kith} PREFIX=${KITH_GUEST_PREFIX:-$HOME/.local} KITH_PUBKEY=RWQhm3bB3gz4f+YdOeWnGIzSXYL09VomfCiTphj+KFfDr1HQE2l4Ufdi say() { printf '%s\n' "$*"; } die() { printf 'install-guest: %s\n' "$*" >&2; exit 1; } have() { command -v "$1" >/dev/null 2>&1; } [ "$(uname -s)" = Linux ] || die "this installs the Linux client; on a Mac use https://raw.githubusercontent.com/$REPO/main/scripts/install.sh" case $(uname -m) in x86_64|amd64) ARCH=x86_64 ;; aarch64|arm64) ARCH=aarch64 ;; *) die "unsupported CPU: $(uname -m)" ;; esac have tar || die "tar is needed" if have sha256sum; then sha256_of() { sha256sum "$1" | cut -d' ' -f1; } elif have shasum; then sha256_of() { shasum -a 256 "$1" | cut -d' ' -f1; } else die "sha256sum (or shasum) is needed to verify the download"; fi # fetch : curl, else wget. if have curl; then fetch() { curl -fsSL -o "$2" "$1"; } elif have wget; then fetch() { wget -q -O "$2" "$1"; } else die "curl (or wget) is needed"; fi WORK=$(mktemp -d "${TMPDIR:-/tmp}/kith-install.XXXXXX") trap 'rm -rf "$WORK"' EXIT # b64bytes : bytes skip+1..skip+count of the decoded base64. b64bytes() { printf '%s' "$1" | base64 -d | tail -c +$(($2 + 1)) | head -c "$3" > "$4"; } # verify_sig : checks a minisign signature against KITH_PUBKEY, with minisign or else OpenSSL 3 # (a minisign signature is Ed25519 over the file's BLAKE2b-512, or over the file itself for the legacy "Ed" kind). # Returns 1 for a bad signature and 2 when there is no tool to check it. verify_sig() { if have minisign; then minisign -Vq -P "$KITH_PUBKEY" -m "$1" -x "$2" >/dev/null 2>&1 || return 1 return 0 fi have openssl && openssl version 2>/dev/null | grep -q '^OpenSSL [3-9]' || return 2 d=$WORK/sig mkdir -p "$d" sig=$(sed -n 2p "$2") b64bytes "$sig" 0 2 "$d/alg" b64bytes "$sig" 2 8 "$d/keyid.sig" b64bytes "$sig" 10 64 "$d/sig" b64bytes "$KITH_PUBKEY" 2 8 "$d/keyid.pub" b64bytes "$KITH_PUBKEY" 10 32 "$d/pk" cmp -s "$d/keyid.sig" "$d/keyid.pub" || return 1 # The Ed25519 key as a DER SubjectPublicKeyInfo: the fixed 12-byte prefix, then the 32 key bytes. { printf '\060\052\060\005\006\003\053\145\160\003\041\000'; cat "$d/pk"; } > "$d/pk.der" case $(cat "$d/alg") in ED) openssl dgst -blake2b512 -binary "$1" > "$d/msg" ;; Ed) cp "$1" "$d/msg" ;; *) return 1 ;; esac openssl pkeyutl -verify -pubin -keyform DER -inkey "$d/pk.der" -rawin -in "$d/msg" -sigfile "$d/sig" >/dev/null 2>&1 } if [ -n "${KITH_INSTALL_TARBALL:-}" ]; then TARBALL=$KITH_INSTALL_TARBALL [ -f "$TARBALL" ] || die "$TARBALL not found" WANT=${KITH_INSTALL_SHA256:-} [ -n "$WANT" ] || { [ -f "$TARBALL.sha256" ] && WANT=$(cut -d' ' -f1 < "$TARBALL.sha256"); } || true else VERSION=${KITH_VERSION:-} if [ -z "$VERSION" ]; then # /releases/latest redirects to /releases/tag/vX.Y.Z. if have curl; then url=$(curl -fsSL -o /dev/null -w '%{url_effective}' "https://github.com/$REPO/releases/latest") || die "could not look up the latest release of $REPO" else url=$(wget -q --max-redirect=0 -S -O /dev/null "https://github.com/$REPO/releases/latest" 2>&1 | sed -n 's/^ *[Ll]ocation: *//p' | tr -d '\r' | head -1) || url= fi VERSION=${url##*/} case $VERSION in v[0-9]*) ;; *) die "could not find the latest release of $REPO (none yet?); set KITH_VERSION" ;; esac fi VERSION=${VERSION#v} ASSET=kith-guest-$VERSION-linux-$ARCH.tar.gz BASE=https://github.com/$REPO/releases/download/v$VERSION say "downloading kith $VERSION (linux $ARCH)..." TARBALL=$WORK/$ASSET fetch "$BASE/$ASSET" "$TARBALL" || die "download failed: $BASE/$ASSET" fetch "$BASE/SHA256SUMS" "$WORK/SHA256SUMS" || die "download failed: $BASE/SHA256SUMS" unsigned() { [ "${KITH_ALLOW_UNSIGNED:-}" = 1 ] || die "$1; not installing (KITH_ALLOW_UNSIGNED=1 installs it on the checksum alone)" say "warning: $1; installing on the checksum alone (KITH_ALLOW_UNSIGNED=1)" } if fetch "$BASE/SHA256SUMS.minisig" "$WORK/SHA256SUMS.minisig"; then rc=0; verify_sig "$WORK/SHA256SUMS" "$WORK/SHA256SUMS.minisig" || rc=$? case $rc in 0) say "signature ok" ;; 2) unsigned "minisign or OpenSSL 3 is needed to check the release signature" ;; *) die "SHA256SUMS of v$VERSION is not signed by the kith release key; not installing" ;; esac else unsigned "release v$VERSION has no signature ($BASE/SHA256SUMS.minisig)" fi WANT=$(awk -v f="$ASSET" '$2 == f || $2 == "*" f { print $1 }' "$WORK/SHA256SUMS") [ -n "$WANT" ] || die "no checksum for $ASSET in SHA256SUMS" fi if [ -n "${WANT:-}" ]; then [ "$(sha256_of "$TARBALL")" = "$WANT" ] || die "checksum mismatch for $TARBALL; not installing" say "checksum ok" fi mkdir -p "$WORK/x" tar -C "$WORK/x" -xzf "$TARBALL" SRC=$(echo "$WORK"/x/kith-guest-*) [ -f "$SRC/bin/kith" ] || die "$TARBALL is not a kith client release" # A new file moved into place by rename, so a running kith is never half-written. mkdir -p "$PREFIX/bin" cp "$SRC/bin/kith" "$PREFIX/bin/.kith.new.$$" && chmod 755 "$PREFIX/bin/.kith.new.$$" && mv -f "$PREFIX/bin/.kith.new.$$" "$PREFIX/bin/kith" # An explicit prefix is the bot's home too: the folder next to bin/ that kith looks for. Chosen once here, so an upgrade # keeps it; an existing bot at ~/.config/kith is left where it is, and the default prefix changes nothing. if [ -n "${KITH_GUEST_PREFIX:-}" ] && [ ! -e "$PREFIX/.kith" ] && [ ! -f "${XDG_CONFIG_HOME:-$HOME/.config}/kith/device.key" ]; then (umask 077; mkdir -p "$PREFIX/.kith") fi say "installed kith to $PREFIX/bin/kith" case ":$PATH:" in *":$PREFIX/bin:"*) ;; *) say "add it to your PATH: export PATH=\"$PREFIX/bin:\$PATH\"" ;; esac say "next: kith join "