#!/bin/sh
# llamay installer for macOS and Linux.
#
#   curl -fsSL https://llamay.com/install.sh | sh
#
# POSIX sh, not bash: this is piped into whatever /bin/sh the machine has, and
# on Debian and Ubuntu that is dash. Every construct here works in both.
#
# What it does, in order, and nothing else:
#
#   1. works out the platform and picks the right artifact
#   2. downloads it and the release's SHA256SUMS, and checks the sum
#   3. installs the binary to $LLAMAY_INSTALL_DIR (default /usr/local/bin)
#   4. on Linux with systemd, installs and starts a service
#   5. on macOS, installs and loads a LaunchAgent
#
# Overridable before it runs:
#
#   LLAMAY_VERSION       a specific version, e.g. 1.2.3 (default: latest)
#   LLAMAY_INSTALL_DIR   where the binary goes (default /usr/local/bin)
#   LLAMAY_NO_SERVICE    set to 1 to install the CLI and no service
#   LLAMAY_ACCEL         cpu|gpu — gpu takes the CUDA+Vulkan build on Linux
#                        amd64, and the Metal build on Apple Silicon; the
#                        default picks gpu where one exists for the platform
set -eu

# The artifacts live in a public repository of their own. The engine's source
# is private, and a private repository answers 404 to an unauthenticated
# download -- which is every user of this script. Same arrangement as
# kosa8-releases and anz33-releases.
REPO="AzmxAI/llamay-releases"
BASE="https://github.com/${REPO}/releases"

say()  { printf '%s\n' "$*"; }
warn() { printf '%s\n' "$*" >&2; }
die()  { printf 'error: %s\n' "$*" >&2; exit 1; }

need() {
	command -v "$1" >/dev/null 2>&1 || die "$1 is required and was not found"
}

# sudo only where it is actually needed. A user who has write access to the
# install directory should not be asked for a password, and a machine with no
# sudo at all should still be able to install into a directory it owns.
SUDO=""
maybe_sudo() {
	if [ -w "$1" ]; then
		SUDO=""
	elif command -v sudo >/dev/null 2>&1; then
		SUDO="sudo"
	elif [ "$(id -u)" = 0 ]; then
		SUDO=""
	else
		die "no write access to $1 and no sudo; set LLAMAY_INSTALL_DIR to a directory you own"
	fi
}

need curl
need tar

OS=$(uname -s)
ARCH=$(uname -m)

case "$ARCH" in
	x86_64 | amd64) ARCH=amd64 ;;
	aarch64 | arm64) ARCH=arm64 ;;
	*) die "unsupported architecture: $ARCH" ;;
esac

case "$OS" in
	Linux) OS=linux ;;
	Darwin) OS=darwin ;;
	*) die "unsupported operating system: $OS — for Windows see https://llamay.com/download/windows" ;;
esac

# Apple Silicon gets the Metal build and Intel Macs do not: the Metal shaders
# need Apple Silicon, and there is no darwin/amd64 GPU artifact to fall back to.
ACCEL="${LLAMAY_ACCEL:-auto}"
if [ "$ACCEL" = auto ]; then
	ACCEL=cpu
	if [ "$OS" = linux ] && [ "$ARCH" = amd64 ]; then ACCEL=gpu; fi
	if [ "$OS" = darwin ] && [ "$ARCH" = arm64 ]; then ACCEL=gpu; fi
fi

VERSION="${LLAMAY_VERSION:-}"
if [ -z "$VERSION" ]; then
	# The redirect on /releases/latest names the tag, which avoids depending on
	# the API and its rate limit for what is one lookup.
	VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' "${BASE}/latest" |
		sed 's|.*/tag/v||')
	[ -n "$VERSION" ] || die "could not determine the latest version; set LLAMAY_VERSION"
fi

case "$ACCEL" in
	gpu)
		if [ "$OS" = linux ]; then
			ASSET="llamay_${VERSION}_linux_amd64_gpu.tar.gz"
		else
			ASSET="llamay_${VERSION}_darwin_arm64_metal.tar.gz"
		fi
		;;
	cpu) ASSET="llamay_${VERSION}_${OS}_${ARCH}.tar.gz" ;;
	*) die "LLAMAY_ACCEL must be cpu, gpu or auto" ;;
esac

INSTALL_DIR="${LLAMAY_INSTALL_DIR:-/usr/local/bin}"
URL="${BASE}/download/v${VERSION}/${ASSET}"

say "llamay ${VERSION} — ${OS}/${ARCH} (${ACCEL})"

TMP=$(mktemp -d)
# The trap is the only cleanup: every path out of here goes through it,
# including the die() calls above this line, which is why it is set as early
# as the directory exists.
trap 'rm -rf "$TMP"' EXIT INT TERM

say "downloading ${ASSET}"
curl -fsSL --retry 3 -o "${TMP}/${ASSET}" "$URL" ||
	die "download failed: $URL"

# The checksum is not optional and not best-effort. A release publishes
# SHA256SUMS over the artifacts as they were uploaded, and an installer that
# skips the check on a machine with no sha256 tool is an installer that runs
# whatever it was handed.
if curl -fsSL --retry 3 -o "${TMP}/SHA256SUMS" "${BASE}/download/v${VERSION}/SHA256SUMS"; then
	if command -v sha256sum >/dev/null 2>&1; then
		SUM=$(sha256sum "${TMP}/${ASSET}" | cut -d' ' -f1)
	elif command -v shasum >/dev/null 2>&1; then
		SUM=$(shasum -a 256 "${TMP}/${ASSET}" | cut -d' ' -f1)
	else
		die "neither sha256sum nor shasum is available to verify the download"
	fi
	WANT=$(grep " ${ASSET}\$" "${TMP}/SHA256SUMS" | cut -d' ' -f1)
	[ -n "$WANT" ] || die "${ASSET} is not listed in SHA256SUMS"
	[ "$SUM" = "$WANT" ] || die "checksum mismatch for ${ASSET}: got ${SUM}, expected ${WANT}"
	say "checksum ok"
else
	die "could not fetch SHA256SUMS for v${VERSION}"
fi

tar -xzf "${TMP}/${ASSET}" -C "$TMP"
[ -f "${TMP}/llamay" ] || die "the archive does not contain a llamay binary"

maybe_sudo "$(dirname "$INSTALL_DIR")"
$SUDO mkdir -p "$INSTALL_DIR"
maybe_sudo "$INSTALL_DIR"
$SUDO install -m 0755 "${TMP}/llamay" "${INSTALL_DIR}/llamay"
say "installed ${INSTALL_DIR}/llamay"

# The binary answers to a second name, and the name deployment tooling looks
# for. It is a symlink rather than a copy so an upgrade moves both at once.
$SUDO ln -sf "${INSTALL_DIR}/llamay" "${INSTALL_DIR}/llamay-server" 2>/dev/null || true

if [ "${LLAMAY_NO_SERVICE:-0}" = 1 ]; then
	say ""
	say "run:  llamay serve"
	exit 0
fi

install_systemd() {
	# A system service needs an account that is not a person's. It gets a home
	# because the model store lives under it, and no shell because nothing
	# should be able to log in as it.
	if ! id llamay >/dev/null 2>&1; then
		$SUDO useradd -r -m -d /var/lib/llamay -s /usr/sbin/nologin llamay 2>/dev/null ||
			$SUDO useradd -r -m -d /var/lib/llamay -s /bin/false llamay 2>/dev/null || true
	fi
	$SUDO mkdir -p /var/lib/llamay
	$SUDO chown -R llamay:llamay /var/lib/llamay 2>/dev/null || true

	$SUDO tee /etc/systemd/system/llamay.service >/dev/null <<-UNIT
	[Unit]
	Description=llamay inference server
	After=network-online.target
	Wants=network-online.target

	[Service]
	ExecStart=${INSTALL_DIR}/llamay serve
	User=llamay
	Group=llamay
	Restart=always
	RestartSec=3
	Environment=LLAMAY_MODELS=/var/lib/llamay/models

	# It listens on loopback by default and reads model files. Nothing here
	# needs to write outside its own state directory or see another user's
	# processes, so it is told it may not.
	NoNewPrivileges=true
	PrivateTmp=true
	ProtectSystem=strict
	ProtectHome=true
	ReadWritePaths=/var/lib/llamay

	[Install]
	WantedBy=multi-user.target
	UNIT

	$SUDO systemctl daemon-reload
	$SUDO systemctl enable --now llamay >/dev/null 2>&1 || {
		warn "the service was installed but did not start; run: systemctl status llamay"
		return
	}
	say "service running — systemctl status llamay"
}

install_launchd() {
	# A LaunchAgent rather than a LaunchDaemon: it runs as the user who
	# installed it, so the model store is the one their CLI already uses, and
	# it needs no root to load.
	AGENTS="${HOME}/Library/LaunchAgents"
	PLIST="${AGENTS}/com.llamay.server.plist"
	mkdir -p "$AGENTS"
	cat >"$PLIST" <<-PLIST
	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
	<plist version="1.0">
	<dict>
	  <key>Label</key><string>com.llamay.server</string>
	  <key>ProgramArguments</key>
	  <array>
	    <string>${INSTALL_DIR}/llamay</string>
	    <string>serve</string>
	  </array>
	  <key>RunAtLoad</key><true/>
	  <key>KeepAlive</key><true/>
	  <key>StandardOutPath</key><string>${HOME}/Library/Logs/llamay.log</string>
	  <key>StandardErrorPath</key><string>${HOME}/Library/Logs/llamay.log</string>
	</dict>
	</plist>
	PLIST

	# bootout before bootstrap, so re-running the installer replaces the agent
	# rather than failing on one that is already loaded.
	launchctl bootout "gui/$(id -u)/com.llamay.server" 2>/dev/null || true
	if launchctl bootstrap "gui/$(id -u)" "$PLIST" 2>/dev/null; then
		say "service running — launchctl print gui/$(id -u)/com.llamay.server"
	else
		warn "the agent was written to ${PLIST} but did not load; run: launchctl bootstrap gui/$(id -u) ${PLIST}"
	fi
}

if [ "$OS" = linux ]; then
	if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
		install_systemd
	else
		warn "no systemd here, so no service was installed"
		say "run:  llamay serve"
	fi
else
	install_launchd
fi

say ""
say "llamay $(${INSTALL_DIR}/llamay version -short 2>/dev/null || echo "$VERSION") is installed"
say "  llamay pull hf:Qwen/Qwen2.5-0.5B-Instruct-GGUF/qwen2.5-0.5b-instruct-q4_k_m.gguf"
say "  llamay run -m qwen2.5-0.5b-instruct:q4_k_m -p 'hello'"
say "  the server is on http://127.0.0.1:8081"
