#!/bin/sh # Copyright (c) 2026 kosa8. All rights reserved. Proprietary; see LICENSE. # kosa8 installer. # # curl -fsSL https://kosa8.com/install.sh | sh # # Detects your platform, downloads the matching release, checks it against the # published SHA-256 sums, and puts it on your PATH. # # What it will NOT do quietly: pretend kosa8 runs everywhere. The part that # actually runs containers needs Apple's Virtualization framework, which only # exists on Apple Silicon Macs. On anything else this installs the command-line # tool alone, and tells you so, because finding that out after installing is # worse than being told now. set -eu REPO="AzmxAI/kosa8-releases" BINDIR="${KOSA8_BINDIR:-/usr/local/bin}" say() { printf '%s\n' "$1"; } info() { printf ' %s\n' "$1"; } warn() { printf '\033[33m %s\033[0m\n' "$1" >&2; } die() { printf '\033[31merror:\033[0m %s\n' "$1" >&2; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || die "$1 is required but not installed"; } need curl need tar # ---- work out what to fetch ------------------------------------------------- os=$(uname -s) arch=$(uname -m) case "$arch" in aarch64) arch=arm64 ;; x86_64) arch=amd64 ;; esac full_runtime=no case "$os/$arch" in Darwin/arm64) asset_os=darwin; ext=zip; full_runtime=yes ;; Darwin/amd64) asset_os=darwin; ext=zip ;; Linux/arm64|Linux/amd64) asset_os=linux; ext=tar.gz; full_runtime=linux ;; *) die "unsupported platform: $os/$arch (kosa8 needs macOS or Linux; on Windows use the PowerShell installer at https://kosa8.com/install.ps1)" ;; esac # ---- resolve the version ---------------------------------------------------- version="${KOSA8_VERSION:-}" if [ -z "$version" ]; then # Resolve the latest tag from the redirect on /releases/latest rather than # the API. The API is rate-limited per IP, so on a CI runner, behind # corporate NAT, or on any shared address it answers 403 and the install # fails for a reason that has nothing to do with the user. The redirect is # plain HTTP with no such limit. version=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \ "https://github.com/$REPO/releases/latest" 2>/dev/null | sed -n 's#.*/tag/##p') if [ -z "$version" ]; then # Fall back to the API, which may still work when the redirect does not. version=$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" 2>/dev/null \ | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -1) fi [ -n "$version" ] || die "could not determine the latest version; set KOSA8_VERSION=v0.9.9 and retry" fi num="${version#v}" asset="kosa8_${num}_${asset_os}_${arch}.${ext}" base="https://github.com/$REPO/releases/download/$version" say "" say " kosa8 $version — $os/$arch" say "" # ---- download and verify ---------------------------------------------------- tmp=$(mktemp -d) cleanup() { rm -rf "$tmp"; } trap cleanup EXIT INT TERM info "downloading $asset" curl -fsSL -o "$tmp/$asset" "$base/$asset" \ || die "could not download $asset — check https://github.com/$REPO/releases" # The checksum is the whole point of scripting this rather than telling people # to click a link: verify before anything is unpacked or run. Every release # publishes checksums.txt, so a missing file or a missing line is not a gap to # shrug past — it is exactly what a tampered or truncated release looks like, # and "continuing without verification" would install it anyway. curl -fsSL -o "$tmp/checksums.txt" "$base/checksums.txt" \ || die "could not download checksums.txt for $version — refusing to install unverified" # The signature over checksums.txt, when the release carries one. Keyless # cosign: the certificate names the workflow that signed, and that identity is # pinned — the one release workflow, AzmxAI/azmx-ai's kosa8-signed-release.yml # (docs/RELEASING.md) — because a signature that verifies against anyone # proves only that somebody signed something. cosign is not a requirement for # installing: without it the checksums are still enforced, and you are told the # signature went unchecked rather than left to assume it was. SIGNER_IDENTITY='^https://github\.com/AzmxAI/azmx-ai/\.github/workflows/kosa8-signed-release\.yml@refs/.+$' SIGNER_ISSUER='https://token.actions.githubusercontent.com' if curl -fsSL -o "$tmp/checksums.txt.sig" "$base/checksums.txt.sig" 2>/dev/null; then if ! command -v cosign >/dev/null 2>&1; then warn "cosign is not installed, so the release signature was not checked (the checksum still is)" warn " install cosign to verify it: https://docs.sigstore.dev/cosign/installation" else curl -fsSL -o "$tmp/checksums.txt.pem" "$base/checksums.txt.pem" \ || die "checksums.txt is signed but its certificate is missing — refusing to install" cosign verify-blob \ --certificate "$tmp/checksums.txt.pem" \ --signature "$tmp/checksums.txt.sig" \ --certificate-identity-regexp "$SIGNER_IDENTITY" \ --certificate-oidc-issuer "$SIGNER_ISSUER" \ "$tmp/checksums.txt" >/dev/null 2>&1 \ || die "the signature on checksums.txt does not verify — refusing to install" info "signature verified" fi fi # Exact name, whole field: `grep " $asset$"` with the name taken as a regex # would let the dots match anything; awk compares the string itself. expected=$(awk -v a="$asset" '$2 == a || $2 == "*" a { print $1; exit }' "$tmp/checksums.txt") [ -n "$expected" ] || die "checksums.txt has no entry for $asset — refusing to install unverified" if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$tmp/$asset" | awk '{print $1}') else actual=$(shasum -a 256 "$tmp/$asset" | awk '{print $1}') fi [ "$actual" = "$expected" ] || die "checksum mismatch for $asset — refusing to install" info "checksum verified" # ---- unpack ----------------------------------------------------------------- if [ "$ext" = "zip" ]; then need unzip unzip -qo "$tmp/$asset" -d "$tmp/x" else mkdir -p "$tmp/x" && tar -xzf "$tmp/$asset" -C "$tmp/x" fi # ---- install ---------------------------------------------------------------- install_one() { src="$1" [ -f "$src" ] || return 0 name=$(basename "$src") if [ -w "$BINDIR" ]; then mv "$src" "$BINDIR/$name" else info "writing to $BINDIR needs sudo" sudo mv "$src" "$BINDIR/$name" fi chmod +x "$BINDIR/$name" 2>/dev/null || sudo chmod +x "$BINDIR/$name" info "installed $name" } mkdir -p "$BINDIR" 2>/dev/null || sudo mkdir -p "$BINDIR" install_one "$tmp/x/kosa8" [ "$full_runtime" != no ] && install_one "$tmp/x/kosa8d" # ---- confirm and explain ---------------------------------------------------- say "" if ! command -v kosa8 >/dev/null 2>&1; then warn "$BINDIR is not on your PATH — add it to your shell profile:" warn " export PATH=\"$BINDIR:\$PATH\"" say "" fi if [ "$full_runtime" = linux ]; then # The Linux engine runs each sandbox under Cloud Hypervisor on KVM. Say # what is missing now, rather than at the first `kosa8 run`. missing="" [ -r /dev/kvm ] && [ -w /dev/kvm ] || missing="$missing /dev/kvm is not usable: enable virtualization, then: sudo usermod -aG kvm \$USER (and log in again)" command -v cloud-hypervisor >/dev/null 2>&1 || missing="$missing cloud-hypervisor is not installed (Debian/Ubuntu: sudo apt-get install cloud-hypervisor)" command -v virtiofsd >/dev/null 2>&1 || [ -x /usr/libexec/virtiofsd ] || missing="$missing virtiofsd is not installed (Debian/Ubuntu: sudo apt-get install virtiofsd)" if [ -n "$missing" ]; then warn "Installed. Before kosa8 can run sandboxes here:$missing" say "" fi say " Then give the daemon its two network capabilities, and start it:" say "" say " sudo setcap cap_net_admin,cap_net_raw+ep \"\$(command -v kosa8d)\"" say " kosa8 daemon start" say " kosa8 run alpine echo hi" say "" say " kosa8 doctor checks all of this." elif [ "$full_runtime" = yes ]; then say " Ready. Start it with:" say "" say " kosa8 daemon start" say " kosa8 run alpine echo hi" else say " Installed the command-line tool." say "" warn "This machine cannot run containers itself." say " kosa8 needs an Apple Silicon Mac or a Linux machine to do the actual" say " work. On that machine run \`kosa8 worker pair --url …\`, then here:" say "" say " kosa8 worker join k8w1.…" say " kosa8 ps" fi say "" say " Free for 14 days. Docs: https://kosa8.com" say ""