#!/bin/sh # Zyvra installer for macOS and Linux. # curl -fsSL /install.sh | sh # # It downloads the newest release, checks the release signature (Ed25519, needs OpenSSL 3) and the # archive's SHA-256, and installs the program for the current user. No sudo, nothing outside the # install folder is touched. # # Settings (environment): # ZYVRA_INSTALL_DIR where to put it (default: $HOME/.local/bin) # ZYVRA_RELEASES_URL where releases are published (a mirror) # ZYVRA_MIRROR_URL where to look when that is busy or down (default: the Zyvra website) # ZYVRA_REQUIRE_SIGNATURE=1 stop if the signature cannot be checked on this machine set -eu BASE="${ZYVRA_RELEASES_URL:-https://gitlab.com/zyvra-release/releases/-/releases/permalink/latest/downloads}" MIRROR="${ZYVRA_MIRROR_URL-https://zyvra-eight.vercel.app}" DEST="${ZYVRA_INSTALL_DIR:-$HOME/.local/bin}" # The release signing key (the same one built into the program). See "How we verify downloads". PUBKEY_PEM_BODY="MCowBQYDK2VwAyEAmwnnIbrQNSlAdROYjXL0aWFNR+CruiXgJGoX3KYD/H8=" say() { printf '%s\n' "$*"; } die() { printf 'zyvra install: %s\n' "$*" >&2; exit 1; } have() { command -v "$1" >/dev/null 2>&1; } case "$(uname -s)" in Darwin) os=darwin ;; Linux) os=linux ;; *) die "this installer is for macOS and Linux. On Windows use install.ps1." ;; esac case "$(uname -m)" in x86_64 | amd64) arch=amd64 ;; arm64 | aarch64) arch=arm64 ;; *) die "no build for the processor $(uname -m)" ;; esac if have curl; then fetch() { curl -fsSL --proto '=https,http' --proto-redir '=https' --max-time 600 -o "$2" "$1"; } elif have wget; then fetch() { wget -q -O "$2" "$1"; } else die "curl or wget is needed"; fi have tar || die "tar is needed" if have sha256sum; then sha() { sha256sum "$1" | cut -d' ' -f1; } elif have shasum; then sha() { shasum -a 256 "$1" | cut -d' ' -f1; } else die "sha256sum or shasum is needed"; fi tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT INT TERM say "Looking up the newest Zyvra release..." # The release host throttles anonymous requests now and then; the website serves the same signed files. got="" for b in "$BASE" "$MIRROR"; do [ -n "$b" ] || continue if { fetch "$b/latest.json" "$tmp/latest.json" && fetch "$b/latest.json.sig" "$tmp/latest.json.sig"; } 2>/dev/null; then got=1; break; fi say "Could not reach $b, trying another address..." done [ -n "$got" ] || die "could not download the release information (the release server may be busy: try again in a few minutes)" # An OpenSSL that can verify Ed25519 signatures of whole files (3.0 or newer; macOS's own is too old). verifier="" for o in openssl /opt/homebrew/opt/openssl@3/bin/openssl /usr/local/opt/openssl@3/bin/openssl /opt/homebrew/bin/openssl; do if have "$o" && "$o" pkeyutl -help 2>&1 | grep -q rawin; then verifier=$o; break; fi done if [ -n "$verifier" ]; then { printf -- '-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n' "$PUBKEY_PEM_BODY"; } >"$tmp/key.pem" "$verifier" base64 -d -A -in "$tmp/latest.json.sig" -out "$tmp/sig.bin" 2>/dev/null || die "the signature file is not valid" "$verifier" pkeyutl -verify -pubin -inkey "$tmp/key.pem" -rawin -in "$tmp/latest.json" -sigfile "$tmp/sig.bin" >/dev/null 2>&1 \ || die "the release information is NOT signed by Zyvra. Nothing was installed." say "Signature OK." elif [ "${ZYVRA_REQUIRE_SIGNATURE:-}" = 1 ]; then die "cannot check the signature here (OpenSSL 3 is needed, for example: brew install openssl@3)" else say "Note: the signature was not checked because OpenSSL 3 is not installed. The download is still" say " checked against its SHA-256. To check the signature yourself later: zyvra verify ." fi # The manifest is plain, indented JSON; read the block of this platform without needing jq. block=$(sed -n "/\"$os-$arch\": {/,/}/p" "$tmp/latest.json") [ -n "$block" ] || die "this release has no build for $os/$arch" url=$(printf '%s\n' "$block" | sed -n 's/.*"url": "\([^"]*\)".*/\1/p') want=$(printf '%s\n' "$block" | sed -n 's/.*"sha256": "\([0-9a-f]*\)".*/\1/p') version=$(sed -n 's/^ "version": "\([^"]*\)".*/\1/p' "$tmp/latest.json") case "$url" in https://* | http://127.0.0.1:* | http://localhost:*) ;; *) die "the release points at a download that is not https" ;; esac [ ${#want} -eq 64 ] || die "the release information is incomplete" say "Downloading Zyvra $version for $os/$arch..." fetch "$url" "$tmp/zyvra.tar.gz" || die "download failed" [ "$(sha "$tmp/zyvra.tar.gz")" = "$want" ] || die "the download does not match its checksum. Nothing was installed." mkdir "$tmp/x" tar -xzf "$tmp/zyvra.tar.gz" -C "$tmp/x" bin=$(find "$tmp/x" -type f -name zyvra | head -n 1) [ -n "$bin" ] || die "the download has no zyvra program in it" chmod 0755 "$bin" "$bin" --version >/dev/null 2>&1 || die "the downloaded program does not run on this machine" mkdir -p "$DEST" cp "$bin" "$DEST/zyvra.new" && mv -f "$DEST/zyvra.new" "$DEST/zyvra" say "" say "Installed Zyvra $version to $DEST/zyvra" case ":$PATH:" in *":$DEST:"*) say "Next: zyvra init my-project && zyvra up" ;; *) say "$DEST is not on your PATH. Add it (for example: echo 'export PATH=\"$DEST:\$PATH\"' >> ~/.zshrc)," say "or run it directly: $DEST/zyvra init my-project && $DEST/zyvra up" ;; esac say "Update later with: zyvra update"